diff --git a/cypress/e2e/assets_spec/assets_manage.cy.ts b/cypress/e2e/assets_spec/assets_manage.cy.ts index 1f89facd0a3..34c554b374e 100644 --- a/cypress/e2e/assets_spec/assets_manage.cy.ts +++ b/cypress/e2e/assets_spec/assets_manage.cy.ts @@ -12,6 +12,9 @@ describe("Asset", () => { const assetSearchPage = new AssetSearchPage(); const assetFilters = new AssetFilters(); const fillFacilityName = "Dummy Facility 1"; + const assetname = "Dummy Camera"; + const locationName = "Dummy Location 1"; + const initiallocationName = "Camera Location"; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -23,6 +26,57 @@ describe("Asset", () => { cy.awaitUrl("/assets"); }); + it("Create & Edit a service history and verify reflection", () => { + assetSearchPage.typeSearchKeyword(assetname); + assetSearchPage.pressEnter(); + assetSearchPage.verifyBadgeContent(assetname); + assetSearchPage.clickAssetByName(assetname); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintonotes(); + assetPage.enterAssetNotes("Dummy Notes"); + assetPage.enterAssetservicedate("01092023"); + assetPage.clickassetupdatebutton(); + assetPage.scrollintoservicehistory(); + assetPage.clickedithistorybutton(); + assetPage.scrollintonotes(); + assetPage.enterAssetNotes("Dummy Notes Editted"); + assetPage.clickassetupdatebutton(); + assetPage.scrollintoservicehistory(); + assetPage.viewassetservicehistorybutton(); + assetPage.openassetservicehistory(); + assetPage.verifyassetupdateservicehistory(); + assetPage.viewassetservicehistorybutton(); + }); + + it("Create a asset transaction and verify history", () => { + assetSearchPage.typeSearchKeyword(assetname); + assetSearchPage.pressEnter(); + assetSearchPage.verifyBadgeContent(assetname); + assetSearchPage.clickAssetByName(assetname); + assetPage.clickupdatedetailbutton(); + assetPage.clickassetlocation(locationName); + assetPage.clickUpdateAsset(); + assetPage.verifyassetlocation(locationName); + assetPage.verifytransactionStatus(initiallocationName, locationName); + }); + + it("Verify Facility Asset Page Redirection", () => { + cy.visit("/facility"); + assetSearchPage.typeSearchKeyword(fillFacilityName); + assetSearchPage.pressEnter(); + facilityPage.verifyFacilityBadgeContent(fillFacilityName); + facilityPage.visitAlreadyCreatedFacility(); + facilityPage.clickManageFacilityDropdown(); + facilityPage.clickCreateAssetFacilityOption(); + facilityPage.verifyfacilitycreateassetredirection(); + facilityPage.verifyassetfacilitybackredirection(); + facilityPage.clickManageFacilityDropdown(); + facilityPage.clickviewAssetFacilityOption(); + facilityPage.verifyfacilityviewassetredirection(); + assetFilters.assertFacilityText(fillFacilityName); + facilityPage.verifyassetfacilitybackredirection(); + }); + it("Delete an Asset", () => { assetPage.openCreatedAsset(); assetPage.interceptDeleteAssetApi(); diff --git a/cypress/e2e/patient_spec/patient_crud.cy.ts b/cypress/e2e/patient_spec/patient_crud.cy.ts index b55732fa440..b22ccdbce13 100644 --- a/cypress/e2e/patient_spec/patient_crud.cy.ts +++ b/cypress/e2e/patient_spec/patient_crud.cy.ts @@ -33,6 +33,7 @@ describe("Patient Creation with consultation", () => { it("Create a new patient with no consultation", () => { patientPage.createPatient(); patientPage.selectFacility("dummy facility"); + patientPage.patientformvisibility(); patientPage.enterPatientDetails( phone_number, emergency_phone_number, @@ -69,6 +70,7 @@ describe("Patient Creation with consultation", () => { patientPage.interceptFacilities(); patientPage.visitUpdatePatientUrl(); patientPage.verifyStatusCode(); + patientPage.patientformvisibility(); updatePatientPage.enterPatientDetails( "Test E2E User Edited", "O+", diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index b939c31405f..45356a1a4e7 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -276,4 +276,78 @@ export class AssetPage { cy.get("#submit").contains("Import").click(); cy.wait("@importAsset").its("response.statusCode").should("eq", 201); } + + clickupdatedetailbutton() { + cy.get("[data-testid=asset-update-button]").click(); + } + + scrollintonotes() { + cy.get("#notes").scrollIntoView(); + } + + enterAssetNotes(text) { + cy.get("#notes").click().clear(); + cy.get("#notes").click().type(text); + } + + enterAssetservicedate(text) { + cy.get("input[name='last_serviced_on']").click(); + cy.get("#date-input").click().type(text); + } + + clickassetupdatebutton() { + cy.get("#submit").click(); + } + + viewassetservicehistorybutton() { + cy.get("#view-service-history").should("be.visible"); + } + + openassetservicehistory() { + cy.get("#view-service-history").click(); + cy.get("#view-asset-edit-history").first().click(); + } + + verifyassetupdateservicehistory() { + cy.get("#edit-history-asset-servicedon").should("have.text", "01/09/2023"); + cy.get("#edit-history-asset-note").should( + "have.text", + "Dummy Notes Editted" + ); + cy.get("#view-history-back-button").contains("Back").click(); + cy.get("#view-history-back-button").contains("Close").click(); + } + + scrollintoservicehistory() { + cy.get("#service-history").scrollIntoView(); + } + + clickedithistorybutton() { + cy.get("#edit-service-history").click(); + } + + verifytransactionStatus(initiallocationName: string, locationName: string) { + cy.get("#transaction-history").scrollIntoView(); + cy.get("#transaction-history table tbody tr:first-child td:eq(0)").should( + "contain", + initiallocationName + ); + cy.get("#transaction-history table tbody tr:first-child td:eq(1)").should( + "contain", + locationName + ); + } + + verifyassetlocation(locationName: string) { + cy.get("#asset-current-location").should("contain", locationName); + } + + clickassetlocation(locationName: string) { + cy.get("#clear-button").click(); + cy.get("[data-testid=asset-location-input] button").click(); + cy.get("[data-testid=asset-location-input] button") + .click() + .type(locationName); + cy.get("[role='option']").contains(locationName).click(); + } } diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 0c12d4655fd..42ec6d8fd7c 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -191,9 +191,7 @@ class FacilityPage { } verifyfacilitycreateassetredirection() { - cy.intercept("GET", "**/api/v1/facility/**").as("getNewAssets"); cy.url().should("include", "/assets/new"); - cy.wait("@getNewAssets").its("response.statusCode").should("eq", 200); } verifyassetfacilitybackredirection() { @@ -205,9 +203,7 @@ class FacilityPage { } verifyfacilityviewassetredirection() { - cy.intercept("GET", "**api/v1/getallfacilities/**").as("getViewAssets"); cy.url().should("include", "/assets?facility="); - cy.wait("@getViewAssets").its("response.statusCode").should("eq", 200); } clickManageInventory() { diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index c0ba0c260d8..b1c95d19140 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -16,6 +16,7 @@ export class PatientConsultationPage { } fillIllnessHistory(history: string) { + cy.wait(5000); cy.get("#history_of_present_illness").scrollIntoView(); cy.get("#history_of_present_illness").should("be.visible"); cy.get("#history_of_present_illness").click().type(history); diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index 9b8df4a287e..4fcd43dc490 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -124,4 +124,8 @@ export class PatientPage { verifyStatusCode() { cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); } + + patientformvisibility() { + cy.get("[data-testid='current-address']").scrollIntoView(); + } } diff --git a/package-lock.json b/package-lock.json index 6f655932fd7..4871fbe374b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "react-player": "^2.12.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", - "react-swipeable-views": "^0.14.0", "react-transition-group": "^4.4.5", "react-webcam": "^7.1.1", "read-excel-file": "^5.6.1", @@ -85,7 +84,6 @@ "@types/react-dom": "^18.2.6", "@types/react-google-recaptcha": "^2.1.5", "@types/react-qr-reader": "^2.1.4", - "@types/react-swipeable-views": "^0.13.2", "@types/react-transition-group": "^4.4.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", @@ -5639,15 +5637,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-swipeable-views": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.13.2.tgz", - "integrity": "sha512-FiszBm9M0JicAgzO/IwDqpfHQRUEjPZA88UexYsVD6qHJBf5LrbGjR5Mw4+yZbf8ZxJneNqOsZbe4WGjOYG7iQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", @@ -11996,11 +11985,6 @@ "node": ">=4.0" } }, - "node_modules/keycode": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", - "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -14273,6 +14257,7 @@ "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -15267,15 +15252,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/npm/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -18125,19 +18101,6 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, - "node_modules/react-event-listener": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", - "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": "^16.3.0" - } - }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -18306,91 +18269,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-swipeable-views": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.14.0.tgz", - "integrity": "sha512-wrTT6bi2nC3JbmyNAsPXffUXLn0DVT9SbbcFr36gKpbaCgEp7rX/OFxsu5hPc/NBsUhHyoSRGvwqJNNrWTwCww==", - "dependencies": { - "@babel/runtime": "7.0.0", - "prop-types": "^15.5.4", - "react-swipeable-views-core": "^0.14.0", - "react-swipeable-views-utils": "^0.14.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/react-swipeable-views-core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.14.0.tgz", - "integrity": "sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA==", - "dependencies": { - "@babel/runtime": "7.0.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/react-swipeable-views-core/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views-core/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, - "node_modules/react-swipeable-views-utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.14.0.tgz", - "integrity": "sha512-W+fXBOsDqgFK1/g7MzRMVcDurp3LqO3ksC8UgInh2P/tKgb5DusuuB1geKHFc6o1wKl+4oyER4Zh3Lxmr8xbXA==", - "dependencies": { - "@babel/runtime": "7.0.0", - "keycode": "^2.1.7", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.0", - "react-swipeable-views-core": "^0.14.0", - "shallow-equal": "^1.2.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/react-swipeable-views-utils/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views-utils/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, - "node_modules/react-swipeable-views/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -19371,11 +19249,6 @@ "node": ">=8" } }, - "node_modules/shallow-equal": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", - "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -21268,14 +21141,6 @@ "makeerror": "1.0.12" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 8ade8c604be..246734dc3d4 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "react-player": "^2.12.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", - "react-swipeable-views": "^0.14.0", "react-transition-group": "^4.4.5", "react-webcam": "^7.1.1", "read-excel-file": "^5.6.1", @@ -125,7 +124,6 @@ "@types/react-dom": "^18.2.6", "@types/react-google-recaptcha": "^2.1.5", "@types/react-qr-reader": "^2.1.4", - "@types/react-swipeable-views": "^0.13.2", "@types/react-transition-group": "^4.4.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", diff --git a/src/App.tsx b/src/App.tsx index e00ca18f652..f89dfd11e74 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,111 +1,28 @@ -import * as Sentry from "@sentry/browser"; - -import { FC, Suspense, lazy, useEffect, useState } from "react"; -import { getConfig, getCurrentUser } from "./Redux/actions"; -import { statusType, useAbortableEffect } from "./Common/utils"; -import { useDispatch, useSelector } from "react-redux"; - -import AppRouter from "./Router/AppRouter"; -import { HistoryAPIProvider } from "./CAREUI/misc/HistoryAPIProvider"; -import { AppConfigContext, IConfig } from "./Common/hooks/useConfig"; -import { LocalStorageKeys } from "./Common/constants"; -import Plausible from "./Components/Common/Plausible"; -import SessionRouter from "./Router/SessionRouter"; -import axios from "axios"; -import { AuthUserContext } from "./Common/hooks/useAuthUser"; - -const Loading = lazy(() => import("./Components/Common/Loading")); - -const App: FC = () => { - const dispatch: any = useDispatch(); - const state: any = useSelector((state) => state); - const { currentUser, config } = state; - const [user, setUser] = useState(null); - - useAbortableEffect(async () => { - const res = await dispatch(getConfig()); - if (res.data && res.status < 400) { - const config = res.data as IConfig; - - if (config?.sentry_dsn && import.meta.env.PROD) { - Sentry.init({ - environment: config.sentry_environment, - dsn: config.sentry_dsn, - }); - } - - localStorage.setItem("config", JSON.stringify(config)); - } - }, [dispatch]); - - const updateRefreshToken = () => { - const refresh = localStorage.getItem(LocalStorageKeys.refreshToken); - // const access = localStorage.getItem(LocalStorageKeys.accessToken); - // if (!access && refresh) { - // localStorage.removeItem(LocalStorageKeys.refreshToken); - // document.location.reload(); - // return; - // } - if (!refresh) { - return; - } - axios - .post("/api/v1/auth/token/refresh/", { - refresh, - }) - .then((resp) => { - localStorage.setItem(LocalStorageKeys.accessToken, resp.data.access); - localStorage.setItem(LocalStorageKeys.refreshToken, resp.data.refresh); - }); - }; - useEffect(() => { - updateRefreshToken(); - setInterval(updateRefreshToken, 5 * 60 * 1000); - }, [user]); - - useAbortableEffect( - async (status: statusType) => { - const res = await dispatch(getCurrentUser()); - if (!status.aborted && res && res.statusCode === 200) { - setUser(res.data); - } - }, - [dispatch] - ); - - useEffect(() => { - const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); - const favicon: any = document.querySelector("link[rel~='icon']"); - if (darkThemeMq.matches) { - favicon.href = "/favicon-light.ico"; - } else { - favicon.href = "/favicon.ico"; - } - }, []); - - if ( - !currentUser || - currentUser.isFetching || - !config || - config.isFetching || - !config.data - ) { - return ; - } - +import { Suspense } from "react"; +import Routers from "./Routers"; +import { + AppConfigProvider, + AuthUserProvider, + HistoryAPIProvider, +} from "./Providers"; +import ThemedFavicon from "./CAREUI/misc/ThemedFavicon"; +import Intergrations from "./Integrations"; +import Loading from "./Components/Common/Loading"; + +const App = () => { return ( }> + - - {currentUser?.data ? ( - - - - ) : ( - - )} - - + + }> + + + + {/* Integrations */} + + + ); diff --git a/src/CAREUI/misc/ThemedFavicon.tsx b/src/CAREUI/misc/ThemedFavicon.tsx new file mode 100644 index 00000000000..55b8d34ec7e --- /dev/null +++ b/src/CAREUI/misc/ThemedFavicon.tsx @@ -0,0 +1,14 @@ +import { useEffect } from "react"; + +export default function ThemedFavicon() { + useEffect(() => { + const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); + const favicon = document.querySelector( + "link[rel~='icon']" + ) as HTMLLinkElement; + + favicon.href = darkThemeMq.matches ? "/favicon-light.ico" : "/favicon.ico"; + }, []); + + return null; +} diff --git a/src/Common/hooks/useAppHistory.ts b/src/Common/hooks/useAppHistory.ts index f4ee2f11f78..a4605db082b 100644 --- a/src/Common/hooks/useAppHistory.ts +++ b/src/Common/hooks/useAppHistory.ts @@ -3,7 +3,7 @@ import { useContext } from "react"; import { HistoryContext, ResetHistoryContext, -} from "../../CAREUI/misc/HistoryAPIProvider"; +} from "../../Providers/HistoryAPIProvider"; export default function useAppHistory() { const history = useContext(HistoryContext); diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx index 309cdc37787..129a696041b 100644 --- a/src/Common/hooks/useFilters.tsx +++ b/src/Common/hooks/useFilters.tsx @@ -196,7 +196,7 @@ export default function useFilters({ limit = 14 }: { limit?: number }) { FilterBadge, FilterBadges, Pagination, - // TODO: update this props to be compliant with new FiltersSlideOver when #3996 is merged. + advancedFilter: { show: showFilters, setShow: setShowFilters, diff --git a/src/Common/hooks/useRangePagination.ts b/src/Common/hooks/useRangePagination.ts index 7652ae546c1..e6bbe9f573e 100644 --- a/src/Common/hooks/useRangePagination.ts +++ b/src/Common/hooks/useRangePagination.ts @@ -9,17 +9,18 @@ interface Props { bounds: DateRange; perPage: number; slots?: number; - defaultEnd?: boolean; + snapToLatest?: boolean; + reverse?: boolean; } const useRangePagination = ({ bounds, perPage, ...props }: Props) => { const [currentRange, setCurrentRange] = useState( - getInitialBounds(bounds, perPage, props.defaultEnd) + getInitialBounds(bounds, perPage, props.snapToLatest) ); useEffect(() => { - setCurrentRange(getInitialBounds(bounds, perPage, props.defaultEnd)); - }, [bounds, perPage, props.defaultEnd]); + setCurrentRange(getInitialBounds(bounds, perPage, props.snapToLatest)); + }, [bounds, perPage, props.snapToLatest]); const next = () => { const { end } = currentRange; @@ -62,17 +63,24 @@ const useRangePagination = ({ bounds, perPage, ...props }: Props) => { } const slots: DateRange[] = []; - const { start } = currentRange; + const { start, end } = currentRange; const delta = perPage / props.slots; for (let i = 0; i < props.slots; i++) { - slots.push({ - start: new Date(start.valueOf() + delta * i), - end: new Date(start.valueOf() + delta * (i + 1)), - }); + if (props.snapToLatest) { + slots.push({ + start: new Date(end.valueOf() - delta * (i - 1)), + end: new Date(end.valueOf() - delta * i), + }); + } else { + slots.push({ + start: new Date(start.valueOf() + delta * i), + end: new Date(start.valueOf() + delta * (i + 1)), + }); + } } - return slots; + return props.reverse ? slots.reverse() : slots; }, [currentRange, props.slots, perPage]); return { @@ -90,7 +98,7 @@ export default useRangePagination; const getInitialBounds = ( bounds: DateRange, perPage: number, - defaultEnd?: boolean + snapToLatest?: boolean ) => { const deltaBounds = bounds.end.valueOf() - bounds.start.valueOf(); @@ -98,7 +106,7 @@ const getInitialBounds = ( return bounds; } - if (defaultEnd) { + if (snapToLatest) { return { start: new Date(bounds.end.valueOf() - perPage), end: bounds.end, diff --git a/src/Components/Assets/AssetConfigure.tsx b/src/Components/Assets/AssetConfigure.tsx index 7099f911463..fd431ae35ea 100644 --- a/src/Components/Assets/AssetConfigure.tsx +++ b/src/Components/Assets/AssetConfigure.tsx @@ -1,60 +1,32 @@ -import { useCallback, useState } from "react"; import Loading from "../Common/Loading"; -import { AssetData } from "./AssetTypes"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { useDispatch } from "react-redux"; -import { getAsset } from "../../Redux/actions"; -import * as Notification from "../../Utils/Notifications.js"; import HL7Monitor from "./AssetType/HL7Monitor"; import ONVIFCamera from "./AssetType/ONVIFCamera"; import Page from "../Common/components/Page"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; interface AssetConfigureProps { assetId: string; facilityId: string; } -const AssetConfigure = (props: AssetConfigureProps) => { - const { assetId, facilityId } = props; - const [asset, setAsset] = useState(); - const [isLoading, setIsLoading] = useState(true); - const [assetType, setAssetType] = useState(""); - const dispatch = useDispatch(); +const AssetConfigure = ({ assetId, facilityId }: AssetConfigureProps) => { + const { + data: asset, + loading, + refetch, + } = useQuery(routes.getAsset, { pathParams: { external_id: assetId } }); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const [assetData]: any = await Promise.all([dispatch(getAsset(assetId))]); - if (!status.aborted) { - setIsLoading(false); - if (!assetData.data) - Notification.Error({ - msg: "Something went wrong..!", - }); - else { - setAsset(assetData.data); - setAssetType(assetData.data.asset_class); - } - } - }, - [dispatch, assetId] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [dispatch, fetchData] - ); - - if (isLoading) return ; + if (loading || !asset) { + return ; + } - if (assetType === "HL7MONITOR") { + if (asset.asset_class === "HL7MONITOR") { return ( { ); } - if (assetType === "VENTILATOR") { + if (asset.asset_class === "VENTILATOR") { return ( { }} backUrl={`/facility/${facilityId}/assets/${assetId}`} > - + refetch()} + /> ); }; diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 27f0f2d8bc0..84325d704aa 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -28,7 +28,7 @@ import RecordMeta from "../../CAREUI/display/RecordMeta"; import { useTranslation } from "react-i18next"; const Loading = lazy(() => import("../Common/Loading")); import * as Notification from "../../Utils/Notifications.js"; -import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import Uptime from "../Common/Uptime"; import useAuthUser from "../../Common/hooks/useAuthUser"; import dayjs from "dayjs"; @@ -231,6 +231,7 @@ const AssetManage = (props: AssetManageProps) => { { setServiceEditData({ ...service, open: true }); @@ -240,6 +241,7 @@ const AssetManage = (props: AssetManageProps) => { {
{item.label}
-
+
{item.content || "--"}
@@ -453,7 +458,6 @@ const AssetManage = (props: AssetManageProps) => { } id="configure-asset" data-testid="asset-configure-button" - authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} > {t("configure")} @@ -514,7 +518,10 @@ const AssetManage = (props: AssetManageProps) => { asset?.asset_class && asset?.asset_class != AssetClass.NONE && }
Service History
-
+
@@ -541,7 +548,10 @@ const AssetManage = (props: AssetManageProps) => {
Transaction History
-
+
diff --git a/src/Components/Assets/AssetServiceEditModal.tsx b/src/Components/Assets/AssetServiceEditModal.tsx index 75c9dd9fc60..66d44d11907 100644 --- a/src/Components/Assets/AssetServiceEditModal.tsx +++ b/src/Components/Assets/AssetServiceEditModal.tsx @@ -97,7 +97,10 @@ export const AssetServiceEditModal = (props: { {edit.edited_by.username}

-
+
@@ -124,19 +127,25 @@ export const AssetServiceEditModal = (props: {

Serviced On

-

+

{formatDate(editRecord.serviced_on)}

Notes

-

{editRecord.note || "-"}

+

+ {editRecord.note || "-"} +

)}
{ editRecord ? setEditRecord(undefined) : props.handleClose(); diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index b19190ed410..55f4d0c258e 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -15,6 +15,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import TextFormField from "../../Form/FormFields/TextFormField"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; +import useAuthUser from "../../../Common/hooks/useAuthUser"; interface HL7MonitorProps { assetId: string; @@ -31,7 +32,7 @@ const HL7Monitor = (props: HL7MonitorProps) => { const [isLoading, setIsLoading] = useState(true); const [localipAddress, setLocalIPAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); - + const authUser = useAuthUser(); const dispatch = useDispatch(); useEffect(() => { @@ -87,40 +88,42 @@ const HL7Monitor = (props: HL7MonitorProps) => { return (
-
- -
-

Connection

-
- setMiddlewareHostname(e.value)} - errorClassName="hidden" - /> - setLocalIPAddress(e.value)} - required - error={ipadrdress_error} - /> - - - Save Configuration - -
- -
- {["HL7MONITOR"].includes(assetType) && ( - - + {["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) && ( +
+ +
+

Connection

+
+ setMiddlewareHostname(e.value)} + errorClassName="hidden" + /> + setLocalIPAddress(e.value)} + required + error={ipadrdress_error} + /> + + + Save Configuration + +
+
- )} -
+ {["HL7MONITOR"].includes(assetType) && ( + + + + )} +
+ )} {assetType === "HL7MONITOR" && ( void; } -const ONVIFCamera = (props: ONVIFCameraProps) => { - const { assetId, facilityId, asset } = props; +const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const [isLoading, setIsLoading] = useState(true); const [assetType, setAssetType] = useState(""); const [middlewareHostname, setMiddlewareHostname] = useState(""); @@ -42,9 +43,8 @@ const ONVIFCamera = (props: ONVIFCameraProps) => { const [refreshPresetsHash, setRefreshPresetsHash] = useState( Number(new Date()) ); - const [refreshHash, setRefreshHash] = useState(Number(new Date())); const dispatch = useDispatch(); - + const authUser = useAuthUser(); useEffect(() => { const fetchFacility = async () => { const res = await dispatch(getPermittedFacility(facilityId)); @@ -87,14 +87,10 @@ const ONVIFCamera = (props: ONVIFCameraProps) => { dispatch(partialUpdateAsset(assetId, data)) ); if (res?.status === 200) { - Notification.Success({ - msg: "Asset Configured Successfully", - }); - setRefreshHash(Number(new Date())); + Notification.Success({ msg: "Asset Configured Successfully" }); + onUpdated?.(); } else { - Notification.Error({ - msg: "Something went wrong..!", - }); + Notification.Error({ msg: "Something went wrong..!" }); } setLoadingSetConfiguration(false); } else { @@ -147,61 +143,62 @@ const ONVIFCamera = (props: ONVIFCameraProps) => { return (
-
-
- setMiddlewareHostname(value)} - /> - setCameraAddress(value)} - error={ipadrdress_error} - /> - setUsername(value)} - /> - setPassword(value)} - /> - setStreamUuid(value)} - /> -
-
- -
- + {["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) && ( +
+
+ setMiddlewareHostname(value)} + /> + setCameraAddress(value)} + error={ipadrdress_error} + /> + setUsername(value)} + /> + setPassword(value)} + /> + setStreamUuid(value)} + /> +
+
+ +
+ + )} {assetType === "ONVIF" ? ( {
Age {" - "} - {props.patientData.age ?? "-"} + {props.patientData.age !== undefined // 0 is a valid age, so we need to check for undefined + ? formatAge( + props.patientData.age, + props.patientData.date_of_birth + ) + : "-"}
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 490c704f295..403f1752704 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -5,10 +5,14 @@ import { SYMPTOM_CHOICES, } from "../../../Common/constants"; import { ConsultationModel, ICD11DiagnosisModel } from "../models"; -import { getConsultation, getPatient } from "../../../Redux/actions"; +import { + getConsultation, + getPatient, + listShiftRequests, +} from "../../../Redux/actions"; import { statusType, useAbortableEffect } from "../../../Common/utils"; import { lazy, useCallback, useState } from "react"; - +import ToolTip from "../../Common/utils/Tooltip"; import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import DischargeModal from "../DischargeModal"; @@ -23,7 +27,7 @@ import { navigate } from "raviger"; import { useDispatch } from "react-redux"; import { useQueryParams } from "raviger"; import { useTranslation } from "react-i18next"; -import { triggerGoal } from "../../Common/Plausible"; +import { triggerGoal } from "../../../Integrations/Plausible"; import useAuthUser from "../../../Common/hooks/useAuthUser"; import { ConsultationUpdatesTab } from "./ConsultationUpdatesTab"; import { ConsultationABGTab } from "./ConsultationABGTab"; @@ -50,10 +54,26 @@ export interface ConsultationTabProps { patientData: PatientModel; } +const TABS = { + UPDATES: ConsultationUpdatesTab, + FEED: ConsultationFeedTab, + SUMMARY: ConsultationSummaryTab, + MEDICINES: ConsultationMedicinesTab, + FILES: ConsultationFilesTab, + INVESTIGATIONS: ConsultationInvestigationsTab, + ABG: ConsultationABGTab, + NURSING: ConsultationNursingTab, + NEUROLOGICAL_MONITORING: ConsultationNeurologicalMonitoringTab, + VENTILATOR: ConsultationVentilatorTab, + NUTRITION: ConsultationNursingTab, + PRESSURE_SORE: ConsultationPressureSoreTab, + DIALYSIS: ConsultationDialysisTab, +}; + export const ConsultationDetails = (props: any) => { const { t } = useTranslation(); const { facilityId, patientId, consultationId } = props; - const tab = props.tab.toUpperCase(); + const tab = props.tab.toUpperCase() as keyof typeof TABS; const dispatch: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [showDoctors, setShowDoctors] = useState(false); @@ -63,6 +83,7 @@ export const ConsultationDetails = (props: any) => { {} as ConsultationModel ); const [patientData, setPatientData] = useState({}); + const [activeShiftingData, setActiveShiftingData] = useState>([]); const [openDischargeSummaryDialog, setOpenDischargeSummaryDialog] = useState(false); const [openDischargeDialog, setOpenDischargeDialog] = useState(false); @@ -124,6 +145,15 @@ export const ConsultationDetails = (props: any) => { }; setPatientData(data); } + + // Get shifting data + const shiftingRes = await dispatch( + listShiftRequests({ patient: id }, "shift-list-call") + ); + if (shiftingRes?.data?.results) { + const data = shiftingRes.data.results; + setActiveShiftingData(data); + } } else { navigate("/not-found"); } @@ -142,22 +172,6 @@ export const ConsultationDetails = (props: any) => { }); }, []); - const TABS = { - UPDATES: ConsultationUpdatesTab, - FEED: ConsultationFeedTab, - SUMMARY: ConsultationSummaryTab, - MEDICINES: ConsultationMedicinesTab, - FILES: ConsultationFilesTab, - INVESTIGATIONS: ConsultationInvestigationsTab, - ABG: ConsultationABGTab, - NURSING: ConsultationNursingTab, - NEUROLOGICAL_MONITORING: ConsultationNeurologicalMonitoringTab, - VENTILATOR: ConsultationVentilatorTab, - NUTRITION: ConsultationNursingTab, - PRESSURE_SORE: ConsultationPressureSoreTab, - DIALYSIS: ConsultationDialysisTab, - }; - const consultationTabProps: ConsultationTabProps = { consultationId, facilityId, @@ -168,6 +182,19 @@ export const ConsultationDetails = (props: any) => { const SelectedTab = TABS[tab]; + const hasActiveShiftingRequest = () => { + if (activeShiftingData.length > 0) { + return [ + "PENDING", + "APPROVED", + "DESTINATION APPROVED", + "PATIENT TO BE PICKED UP", + ].includes(activeShiftingData[activeShiftingData.length - 1].status); + } + + return false; + }; + if (isLoading) { return ; } @@ -191,10 +218,20 @@ export const ConsultationDetails = (props: any) => { return diagnoses.length ? (

{label}

- - {diagnoses.slice(0, !showMore ? nshow : undefined).map((diagnosis) => ( -

{diagnosis.label}

- ))} + {diagnoses.slice(0, !showMore ? nshow : undefined).map((diagnosis) => + diagnosis.id === consultationData.icd11_principal_diagnosis ? ( +
+

{diagnosis.label}

+
+ + + +
+
+ ) : ( +

{diagnosis.label}

+ ) + )} {diagnoses.length > nshow && ( <> {!showMore ? ( @@ -255,17 +292,33 @@ export const ConsultationDetails = (props: any) => {
{!consultationData.discharge_date && (
- - navigate( - `/facility/${patientData.facility}/patient/${patientData.id}/shift/new` - ) - } - className="btn btn-primary m-1 w-full hover:text-white" - > - - Shift Patient - + {hasActiveShiftingRequest() ? ( + + navigate( + `/shifting/${ + activeShiftingData[activeShiftingData.length - 1].id + }` + ) + } + className="btn btn-primary m-1 w-full hover:text-white" + > + + Track Shifting + + ) : ( + + navigate( + `/facility/${patientData.facility}/patient/${patientData.id}/shift/new` + ) + } + className="btn btn-primary m-1 w-full hover:text-white" + > + + Shift Patient + + )}
)*/} - {consultationData.icd11_principal_diagnosis && ( - - d.id === consultationData.icd11_principal_diagnosis - )!, - ]} - /> - )} - { { setIsLoading(true); const res = await dispatchAction(getPatient({ id: patientId })); if (res.data) { - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); if (isUpdate) { - const form = { ...state.form }; - form.action = TELEMEDICINE_ACTIONS.find( - (a) => a.id === res.data.action - )?.text; - dispatch({ type: "set_form", form }); + dispatch({ + type: "set_form", + form: { ...state.form, action: res.data.action }, + }); } + setPatientName(res.data.name); + setFacilityName(res.data.facility_object.name); } } else { setPatientName(""); @@ -302,6 +301,49 @@ export const ConsultationForm = (props: any) => { !!state.form.symptoms.length && !state.form.symptoms.includes(1); const isOtherSymptomsSelected = state.form.symptoms.includes(9); + const handleFormFieldChange: FieldChangeEventHandler = (event) => { + if (event.name === "consultation_status" && event.value === "1") { + dispatch({ + type: "set_form", + form: { + ...state.form, + consultation_status: 1, + symptoms: [1], + symptoms_onset_date: new Date(), + category: "Critical", + suggestion: "DD", + }, + }); + } else if (event.name === "suggestion" && event.value === "DD") { + dispatch({ + type: "set_form", + form: { + ...state.form, + suggestion: "DD", + consultation_notes: "Patient declared dead", + verified_by: "Declared Dead", + }, + }); + } else if ( + event.name === "icd11_diagnoses_object" || + event.name === "icd11_provisional_diagnoses_object" + ) { + dispatch({ + type: "set_form", + form: { + ...state.form, + [event.name]: event.value, + icd11_principal_diagnosis: undefined, + }, + }); + } else { + dispatch({ + type: "set_form", + form: { ...state.form, [event.name]: event.value }, + }); + } + }; + const fetchData = useCallback( async (status: statusType) => { if (!patientId) setIsLoading(true); @@ -352,7 +394,7 @@ export const ConsultationForm = (props: any) => { death_confirmed_doctor: res.data?.death_confirmed_doctor || "", InvestigationAdvice: res.data.investigation, }; - dispatch({ type: "set_form", form: formData }); + dispatch({ type: "set_form", form: { ...state.form, ...formData } }); setBed(formData.bed); if (res.data.last_daily_round) { @@ -364,7 +406,7 @@ export const ConsultationForm = (props: any) => { setIsLoading(false); } }, - [dispatchAction, id] + [dispatchAction, id, patientName, patientId] ); useAbortableEffect( @@ -745,49 +787,6 @@ export const ConsultationForm = (props: any) => { } }; - const handleFormFieldChange: FieldChangeEventHandler = (event) => { - if (event.name === "consultation_status" && event.value === "1") { - dispatch({ - type: "set_form", - form: { - ...state.form, - consultation_status: 1, - symptoms: [1], - symptoms_onset_date: new Date(), - category: "Critical", - suggestion: "DD", - }, - }); - } else if (event.name === "suggestion" && event.value === "DD") { - dispatch({ - type: "set_form", - form: { - ...state.form, - suggestion: "DD", - consultation_notes: "Patient declared dead", - verified_by: "Declared Dead", - }, - }); - } else if ( - event.name === "icd11_diagnoses_object" || - event.name === "icd11_provisional_diagnoses_object" - ) { - dispatch({ - type: "set_form", - form: { - ...state.form, - [event.name]: event.value, - icd11_principal_diagnosis: undefined, - }, - }); - } else { - dispatch({ - type: "set_form", - form: { ...state.form, [event.name]: event.value }, - }); - } - }; - const handleDoctorSelect = (event: FieldChangeEvent) => { if (event.value?.id) { dispatch({ @@ -1347,12 +1346,12 @@ export const ConsultationForm = (props: any) => {
option.desc} - optionValue={(option) => option.text} + optionDescription={() => ""} />
diff --git a/src/Components/Facility/Consultations/Feed.tsx b/src/Components/Facility/Consultations/Feed.tsx index 84b3e62ef9e..75ab72d168c 100644 --- a/src/Components/Facility/Consultations/Feed.tsx +++ b/src/Components/Facility/Consultations/Feed.tsx @@ -30,7 +30,7 @@ import { useDispatch } from "react-redux"; import { useHLSPLayer } from "../../../Common/hooks/useHLSPlayer"; import useKeyboardShortcut from "use-keyboard-shortcut"; import useFullscreen from "../../../Common/hooks/useFullscreen.js"; -import { triggerGoal } from "../../Common/Plausible.js"; +import { triggerGoal } from "../../../Integrations/Plausible.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; interface IFeedProps { diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index 8376a0ad299..b069b371a72 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -75,7 +75,7 @@ const DischargeModal = ({ const [latestClaim, setLatestClaim] = useState(); const [isCreateClaimLoading, setIsCreateClaimLoading] = useState(false); const [isSendingDischargeApi, setIsSendingDischargeApi] = useState(false); - const [facility, setFacility] = useState({ id: 0, name: "" }); // for referred to external + const [facility, setFacility] = useState(); const [errors, setErrors] = useState({}); const fetchLatestClaim = useCallback(async () => { @@ -186,12 +186,13 @@ const DischargeModal = ({ const prescriptionActions = PrescriptionActions(consultationData.id ?? ""); const handleFacilitySelect = (selected: FacilityModel) => { - setFacility(selected ? selected : facility); - const { id, name } = selected; + setFacility(selected); + const { id, name } = selected || {}; const isExternal = id === -1; setPreDischargeForm((prev) => ({ ...prev, - ...(isExternal ? { referred_to_external: name } : { referred_to: id }), + referred_to: isExternal ? null : id, + referred_to_external: isExternal ? name : null, })); }; @@ -238,8 +239,8 @@ const DischargeModal = ({ handleFacilitySelect(selected as FacilityModel) } selected={facility} - showAll={true} - freeText={true} + showAll + freeText multiple={false} errors={errors?.referred_to} className="mb-4" diff --git a/src/Components/Facility/Investigations/Reports/ReportTable.tsx b/src/Components/Facility/Investigations/Reports/ReportTable.tsx index 0826ff904fc..1f20ec94180 100644 --- a/src/Components/Facility/Investigations/Reports/ReportTable.tsx +++ b/src/Components/Facility/Investigations/Reports/ReportTable.tsx @@ -2,7 +2,7 @@ import { getColorIndex, rowColor, transformData } from "./utils"; import ButtonV2 from "../../../Common/components/ButtonV2"; import { InvestigationResponse } from "./types"; -import { formatDateTime } from "../../../../Utils/utils"; +import { formatAge, formatDateTime } from "../../../../Utils/utils"; import { FC } from "react"; const ReportRow = ({ data, name, min, max }: any) => { @@ -53,6 +53,7 @@ interface ReportTableProps { patientDetails?: { name: string; age: number; + date_of_birth: string; hospitalName: string; }; investigationData: InvestigationResponse; @@ -83,7 +84,14 @@ const ReportTable: FC = ({ {patientDetails && (

Name: {patientDetails.name}

-

Age: {patientDetails.age}

+

+ Age:{" "} + {formatAge( + patientDetails.age, + patientDetails.date_of_birth, + true + )} +

Hospital: {patientDetails.hospitalName}

)} diff --git a/src/Components/Facility/Investigations/Reports/index.tsx b/src/Components/Facility/Investigations/Reports/index.tsx index c4c69bc9169..ab5380c62ca 100644 --- a/src/Components/Facility/Investigations/Reports/index.tsx +++ b/src/Components/Facility/Investigations/Reports/index.tsx @@ -100,8 +100,9 @@ const InvestigationReports = ({ id }: any) => { const [patientDetails, setPatientDetails] = useState<{ name: string; age: number; + date_of_birth: string; hospitalName: string; - }>({ name: "", age: -1, hospitalName: "" }); + }>({ name: "", age: -1, date_of_birth: "", hospitalName: "" }); const [state, dispatch] = useReducer( investigationReportsReducer, initialState @@ -220,6 +221,7 @@ const InvestigationReports = ({ id }: any) => { setPatientDetails({ name: res.data.name, age: res.data.age, + date_of_birth: res.data.date_of_birth, hospitalName: res.data.facility_object.name, }); } @@ -227,6 +229,7 @@ const InvestigationReports = ({ id }: any) => { setPatientDetails({ name: "", age: -1, + date_of_birth: "", hospitalName: "", }); } diff --git a/src/Components/Facility/LegacyMonitorCard.tsx b/src/Components/Facility/LegacyMonitorCard.tsx index 9f9fb8f1ea0..61bff3d607b 100644 --- a/src/Components/Facility/LegacyMonitorCard.tsx +++ b/src/Components/Facility/LegacyMonitorCard.tsx @@ -4,6 +4,7 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import { PatientModel } from "../Patient/models"; import LegacyPatientVitalsCard from "../Patient/LegacyPatientVitalsCard"; import { AssetLocationObject } from "../Assets/AssetTypes"; +import { formatAge } from "../../Utils/utils"; interface MonitorCardProps { facilityId: string; @@ -28,7 +29,7 @@ export const LegacyMonitorCard = ({ {patient.name} - {patient.age}y |{" "} + {formatAge(patient.age, patient.date_of_birth)} |{" "} {GENDER_TYPES.find((g) => g.id === patient.gender)?.icon} diff --git a/src/Components/Facility/TreatmentSummary.tsx b/src/Components/Facility/TreatmentSummary.tsx index dbca307d38b..3fbd80ac8b2 100644 --- a/src/Components/Facility/TreatmentSummary.tsx +++ b/src/Components/Facility/TreatmentSummary.tsx @@ -10,7 +10,7 @@ import { statusType, useAbortableEffect } from "../../Common/utils"; import { PatientModel } from "../Patient/models"; import { GENDER_TYPES } from "../../Common/constants"; -import { formatDate, formatDateTime } from "../../Utils/utils"; +import { formatAge, formatDate, formatDateTime } from "../../Utils/utils"; const Loading = lazy(() => import("../Common/Loading")); const TreatmentSummary = (props: any) => { @@ -132,7 +132,8 @@ const TreatmentSummary = (props: any) => {
- Age : {patientData.age} + Age :{" "} + {formatAge(patientData.age, patientData.date_of_birth, true)}
Date of admission : diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index cc34140e7a9..de2c6af698a 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -223,5 +223,4 @@ export interface CurrentBed { export type ICD11DiagnosisModel = { id: string; label: string; - parentId: string | null; }; diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index d06067af957..5f33c6388c5 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -7,6 +7,7 @@ import { MultiSelectOptionChip, dropdownOptionClassNames, } from "./MultiSelectMenuV2"; +import { useTranslation } from "react-i18next"; interface Props { name?: string; @@ -23,6 +24,7 @@ interface Props { placeholder?: string; disabled?: boolean; error?: string; + required?: boolean; onBlur?: () => void; onFocus?: () => void; } @@ -42,11 +44,13 @@ const AutoCompleteAsync = (props: Props) => { className = "", placeholder, disabled = false, + required = false, error, } = props; const [data, setData] = useState([]); const [query, setQuery] = useState(""); const [loading, setLoading] = useState(false); + const { t } = useTranslation(); const hasSelection = (!multiple && selected) || (multiple && selected?.length > 0); @@ -86,9 +90,7 @@ const AutoCompleteAsync = (props: Props) => { : placeholder || "Start typing to search..." } displayValue={() => - hasSelection && !multiple - ? optionLabel && optionLabel(selected) - : "" + hasSelection && !multiple ? optionLabel?.(selected) : "" } onChange={({ target }) => setQuery(target.value)} onFocus={props.onFocus} @@ -100,6 +102,20 @@ const AutoCompleteAsync = (props: Props) => { />
+ {hasSelection && !loading && !required && ( +
+ { + e.preventDefault(); + onChange(null); + }} + /> + + {t("clear_selection")} + +
+ )} {loading ? ( ) : ( diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx index bbc60643faf..c4b3ea3686e 100644 --- a/src/Components/Form/FormFields/Autocomplete.tsx +++ b/src/Components/Form/FormFields/Autocomplete.tsx @@ -165,7 +165,7 @@ export const Autocomplete = (props: AutocompleteProps) => { {value?.icon} {value && !props.isLoading && !props.required && ( -
+
{ diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index dc2dc3115e8..93c4a9e2192 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -48,6 +48,10 @@ 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(), @@ -55,7 +59,8 @@ export default function PrescriptionAdministrationsTable({ }, perPage: 24 * 60 * 60 * 1000, slots: 24, - defaultEnd: true, + snapToLatest: true, + reverse: true, }); const [showBulkAdminister, setShowBulkAdminister] = useState(false); @@ -65,8 +70,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({ @@ -75,7 +85,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(); @@ -142,17 +159,22 @@ export default function PrescriptionAdministrationsTable({ } /> -
-
+
+
- - - )) - : pagination.slots?.map(({ start, end }, index) => ( - - ))} + : pagination.slots + ?.map(({ start, end }, index) => ( + + )) + .reverse()}
{t("medicine")} -

Dosage &

-

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

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

Dosage &

+

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

+
+
@@ -163,8 +185,10 @@ export default function PrescriptionAdministrationsTable({ border className="mx-2 px-1" variant="secondary" - disabled={!pagination.hasPrevious} - onClick={pagination.previous} + disabled={!pagination.hasNext} + onClick={pagination.next} + tooltip="Next 24 hours" + tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" > @@ -178,24 +202,26 @@ export default function PrescriptionAdministrationsTable({

-

{formatDateTime(start, "DD/MM")}

-

{formatDateTime(start, "HH:mm")}

- - - Administration(s) between -
- {formatTime(start)} and{" "} - {formatTime(end)} -
- on {formatDate(start)} -
-
+

{formatDateTime(end, "DD/MM")}

+

{formatDateTime(end, "HH:mm")}

+ + + Administration(s) between +
+ {formatTime(start)} and{" "} + {formatTime(end)} +
+ on {formatDate(start)} +
+
@@ -228,6 +256,23 @@ export default function PrescriptionAdministrationsTable({
+ {showDiscontinued === false && !!discontinuedCount && ( + setShowDiscontinued(true)} + > + + + + Show {discontinuedCount} other discontinued + prescription(s) + + + + )} + {state?.prescriptions.length === 0 && (
@@ -367,65 +412,70 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { )} 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) => ( - - {administrations === undefined ? ( - - ) : ( - - )} - - ))} + {props.intervals + .map(({ start, end }, index) => ( + + {administrations === undefined ? ( + + ) : ( + + )} + + )) + .reverse()} {/* Action Buttons */} diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 71a3ad08c8c..6e83eb9bc91 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -36,15 +36,14 @@ import RecordMeta from "../../CAREUI/display/RecordMeta"; import SearchInput from "../Form/SearchInput"; import SortDropdownMenu from "../Common/SortDropdown"; import SwitchTabs from "../Common/components/SwitchTabs"; -import SwipeableViews from "react-swipeable-views"; import { parseOptionId } from "../../Common/utils"; -import { parsePhoneNumber } from "../../Utils/utils.js"; +import { formatAge, parsePhoneNumber } from "../../Utils/utils.js"; import { useDispatch } from "react-redux"; import useFilters from "../../Common/hooks/useFilters"; import { useTranslation } from "react-i18next"; import Page from "../Common/components/Page.js"; import dayjs from "dayjs"; -import { triggerGoal } from "../Common/Plausible.js"; +import { triggerGoal } from "../../Integrations/Plausible.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -559,7 +558,9 @@ export const PatientManager = () => {
{patient.name} - {`${patient.age} yrs.`} + + {formatAge(patient.age, patient.date_of_birth, true)} +
@@ -985,14 +986,12 @@ export const PatientManager = () => {
- - -
{managePatients}
-
- -
{managePatients}
-
-
+ +
{managePatients}
+
+ +
{managePatients}
+
import("../Common/Loading")); @@ -476,7 +476,12 @@ export const PatientHome = (props: any) => {

- {patientData.name} - {patientData.age} + {patientData.name} -{" "} + {formatAge( + patientData.age, + patientData.date_of_birth, + true + )}

{patientData.is_vaccinated ? ( diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index dbb4aac34ce..701b76b5e3a 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -20,7 +20,7 @@ import { PatientModel } from "./models"; import { getDimensionOrDash } from "../../Common/utils"; import useConfig from "../../Common/hooks/useConfig"; import { useState } from "react"; -import { formatDate, formatDateTime } from "../../Utils/utils.js"; +import { formatAge, formatDate, formatDateTime } from "../../Utils/utils.js"; import dayjs from "../../Utils/dayjs"; export default function PatientInfoCard(props: { @@ -171,7 +171,7 @@ export default function PatientInfoCard(props: {

)}

- {patient.age} years + {formatAge(patient.age, patient.date_of_birth, true)} {patient.gender} {consultation?.suggestion === "DC" && ( diff --git a/src/Components/Patient/PatientNotes.tsx b/src/Components/Patient/PatientNotes.tsx index b7993ed1ea3..68a5ab4dc5d 100644 --- a/src/Components/Patient/PatientNotes.tsx +++ b/src/Components/Patient/PatientNotes.tsx @@ -101,10 +101,6 @@ const PatientNotes = (props: PatientNotesProps) => { }); }; - if (isLoading) { - return ; - } - return (

{

Add new notes