diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 06b1cf8eff0..e5b3c768c49 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -180,6 +180,48 @@ jobs:
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
+ deploy-staging-gcp:
+ needs: build-production
+ name: Deploy to staging GCP cluster
+ runs-on: ubuntu-latest
+ environment:
+ name: Staging-GCP
+ url: https://care-staging.ohc.network/
+ steps:
+ - name: Checkout Kube Config
+ uses: actions/checkout@v3
+ with:
+ repository: coronasafe/care-staging-gcp
+ token: ${{ secrets.GIT_ACCESS_TOKEN }}
+ path: kube
+ ref: main
+
+ # Setup gcloud CLI
+ - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7
+ with:
+ service_account_key: ${{ secrets.GKE_SA_KEY }}
+ project_id: ${{ secrets.GKE_PROJECT }}
+
+ # Get the GKE credentials so we can deploy to the cluster
+ - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e
+ with:
+ cluster_name: ${{ secrets.GKE_CLUSTER }}
+ location: ${{ secrets.GKE_ZONE }}
+ credentials: ${{ secrets.GKE_SA_KEY }}
+
+ - name: install kubectl
+ uses: azure/setup-kubectl@v3.0
+ with:
+ version: "v1.23.6"
+ id: install
+
+ - name: Deploy Care Fe Production
+ run: |
+ mkdir -p $HOME/.kube/
+ cd kube/deployments/
+ sed -i -e "s/_BUILD_NUMBER_/${GITHUB_RUN_NUMBER}/g" care-fe.yaml
+ kubectl apply -f care-fe.yaml
+
deploy-production-manipur:
needs: build-production
name: Deploy to GKE Manipur
diff --git a/cypress/e2e/users_spec/user_creation.cy.ts b/cypress/e2e/users_spec/user_creation.cy.ts
index 498697566c5..bd5002d386f 100644
--- a/cypress/e2e/users_spec/user_creation.cy.ts
+++ b/cypress/e2e/users_spec/user_creation.cy.ts
@@ -3,18 +3,58 @@ import LoginPage from "../../pageobject/Login/LoginPage";
import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch";
import FacilityPage from "../../pageobject/Facility/FacilityCreation";
import { UserPage } from "../../pageobject/Users/UserSearch";
+import { UserCreationPage } from "../../pageobject/Users/UserCreation";
+import {
+ emergency_phone_number,
+ phone_number,
+} from "../../pageobject/constants";
describe("User Creation", () => {
const userPage = new UserPage();
const loginPage = new LoginPage();
+ const userCreationPage = new UserCreationPage();
const facilityPage = new FacilityPage();
const assetSearchPage = new AssetSearchPage();
const fillFacilityName = "Dummy Facility 1";
+ const makeid = (length: number) => {
+ let result = "";
+ const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
+ const charactersLength = characters.length;
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+ };
+ const username = makeid(25);
const alreadylinkedusersviews = [
"devdoctor",
"devstaff2",
"devdistrictadmin",
];
+ const EXPECTED_ERROR_MESSAGES = [
+ "Please select the User Type",
+ "Please enter valid phone number",
+ "Please enter the username",
+ "Please enter date in YYYY/MM/DD format",
+ "Please enter the password",
+ "Confirm password is required",
+ "First Name is required",
+ "Last Name is required",
+ "Please enter a valid email address",
+ "Please select the Gender",
+ "Please select the state",
+ "Please select the district",
+ "Please select the local body",
+ ];
+
+ const EXPECTED_PROFILE_ERROR_MESSAGES = [
+ "Field is required",
+ "Field is required",
+ "This field is required",
+ "Please enter valid phone number",
+ "This field is required",
+ "This field is required",
+ ];
before(() => {
loginPage.loginAsDisctrictAdmin();
@@ -26,6 +66,122 @@ describe("User Creation", () => {
cy.awaitUrl("/users");
});
+ it("Update the existing user profile and verify its reflection", () => {
+ userCreationPage.clickElementById("profilenamelink");
+ userCreationPage.verifyElementContainsText(
+ "username-profile-details",
+ "devdistrictadmin"
+ );
+ userCreationPage.clickElementById("edit-cancel-profile-button");
+ userCreationPage.typeIntoElementByIdPostClear(
+ "firstName",
+ "District Editted"
+ );
+ userCreationPage.typeIntoElementByIdPostClear("lastName", "Cypress");
+ userCreationPage.typeIntoElementByIdPostClear("age", "22");
+ userCreationPage.selectDropdownOption("gender", "Male");
+ userCreationPage.typeIntoElementByIdPostClear(
+ "phoneNumber",
+ "+91" + phone_number
+ );
+ userCreationPage.typeIntoElementByIdPostClear(
+ "altPhoneNumber",
+ "+91" + emergency_phone_number
+ );
+ userCreationPage.typeIntoElementByIdPostClear("email", "test@test.com");
+ userCreationPage.typeIntoElementByIdPostClear("weekly_working_hours", "14");
+ userCreationPage.clickElementById("submit");
+ userCreationPage.verifyElementContainsText(
+ "contactno-profile-details",
+ "+91" + phone_number
+ );
+ userCreationPage.verifyElementContainsText(
+ "whatsapp-profile-details",
+ "+91" + emergency_phone_number
+ );
+ userCreationPage.verifyElementContainsText(
+ "firstname-profile-details",
+ "District Editted"
+ );
+ userCreationPage.verifyElementContainsText(
+ "lastname-profile-details",
+ "Cypress"
+ );
+ userCreationPage.verifyElementContainsText("age-profile-details", "22");
+ userCreationPage.verifyElementContainsText(
+ "emailid-profile-details",
+ "test@test.com"
+ );
+ userCreationPage.verifyElementContainsText(
+ "gender-profile-details",
+ "Male"
+ );
+ userCreationPage.verifyElementContainsText(
+ "averageworkinghour-profile-details",
+ "14"
+ );
+ });
+
+ it("Update the existing user profile Form Mandatory File Error", () => {
+ userCreationPage.clickElementById("profilenamelink");
+ userCreationPage.clickElementById("edit-cancel-profile-button");
+ userCreationPage.clearIntoElementById("firstName");
+ userCreationPage.clearIntoElementById("lastName");
+ userCreationPage.clearIntoElementById("age");
+ userCreationPage.clearIntoElementById("phoneNumber");
+ userCreationPage.clearIntoElementById("altPhoneNumber");
+ userCreationPage.clearIntoElementById("weekly_working_hours");
+ userCreationPage.clickElementById("submit");
+ userCreationPage.verifyErrorMessages(EXPECTED_PROFILE_ERROR_MESSAGES);
+ });
+
+ it("create new user and verify reflection", () => {
+ userCreationPage.clickElementById("addUserButton");
+ userCreationPage.selectFacility("Dummy Shifting Center");
+ userCreationPage.typeIntoElementById("username", username);
+ userCreationPage.typeIntoElementById("password", "Test@123");
+ userCreationPage.selectHomeFacility("Dummy Shifting Center");
+ userCreationPage.typeIntoElementById("phone_number", phone_number);
+ userCreationPage.setInputDate("date_of_birth", "date-input", "25081999");
+ userCreationPage.selectDropdownOption("user_type", "Doctor");
+ userCreationPage.typeIntoElementById("c_password", "Test@123");
+ userCreationPage.typeIntoElementById("doctor_qualification", "MBBS");
+ userCreationPage.typeIntoElementById("doctor_experience_commenced_on", "2");
+ userCreationPage.typeIntoElementById(
+ "doctor_medical_council_registration",
+ "123456789"
+ );
+ userCreationPage.typeIntoElementById("first_name", "cypress test");
+ userCreationPage.typeIntoElementById("last_name", "staff user");
+ userCreationPage.typeIntoElementById("email", "test@test.com");
+ userCreationPage.selectDropdownOption("gender", "Male");
+ userCreationPage.selectDropdownOption("state", "Kerala");
+ userCreationPage.selectDropdownOption("district", "Ernakulam");
+ userCreationPage.clickElementById("submit");
+ userCreationPage.verifyNotification("User added successfully");
+ userPage.typeInSearchInput(username);
+ userPage.checkUsernameText(username);
+ userCreationPage.verifyElementContainsText("name", "cypress test");
+ userCreationPage.verifyElementContainsText("role", "Doctor");
+ userCreationPage.verifyElementContainsText("district", "Ernakulam");
+ userCreationPage.verifyElementContainsText(
+ "home_facility",
+ "Dummy Shifting Center"
+ );
+ userCreationPage.verifyElementContainsText("doctor-qualification", "MBBS");
+ userCreationPage.verifyElementContainsText("doctor-experience", "2");
+ userCreationPage.verifyElementContainsText(
+ "medical-council-registration",
+ "123456789"
+ );
+ });
+
+ it("create new user form throwing mandatory field error", () => {
+ userCreationPage.clickElementById("addUserButton");
+ userCreationPage.clickElementById("submit");
+ userCreationPage.verifyErrorMessages(EXPECTED_ERROR_MESSAGES);
+ });
+
it("view user redirection from facility page", () => {
cy.visit("/facility");
assetSearchPage.typeSearchKeyword(fillFacilityName);
@@ -37,6 +193,18 @@ describe("User Creation", () => {
userPage.verifyMultipleBadgesWithSameId(alreadylinkedusersviews);
});
+ // the below commented out codes, will be used in the upcoming refactoring of this module, since it is a inter-dependent of partially converted code, currently taking it down
+
+ // it("link facility for user", () => {
+ // cy.contains("Linked Facilities").click();
+ // cy.intercept(/\/api\/v1\/facility/).as("getFacilities");
+ // cy.get("[name='facility']")
+ // .click()
+ // .type("Dummy Facility 1")
+ // .wait("@getFacilities");
+ // cy.get("li[role='option']").should("not.exist");
+ // });
+
afterEach(() => {
cy.saveLocalStorage();
});
diff --git a/cypress/e2e/users_spec/user_crud.cy.ts b/cypress/e2e/users_spec/user_crud.cy.ts
deleted file mode 100644
index 91104af518f..00000000000
--- a/cypress/e2e/users_spec/user_crud.cy.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { afterEach, before, beforeEach, cy, describe, it } from "local-cypress";
-
-const makeid = (length: number) => {
- let result = "";
- const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
- const charactersLength = characters.length;
- for (let i = 0; i < length; i++) {
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
- }
- return result;
-};
-
-const username = makeid(25);
-const phone_number = "9999999999";
-const alt_phone_number = "9999999999";
-
-describe("User management", () => {
- before(() => {
- cy.loginByApi("devdistrictadmin", "Coronasafe@123");
- cy.saveLocalStorage();
- });
-
- beforeEach(() => {
- cy.restoreLocalStorage();
- cy.awaitUrl("/user");
- });
-
- it("create user", () => {
- cy.contains("Add New User").click();
- cy.get("[id='user_type'] > div > button").click();
- cy.get("div").contains("Ward Admin").click();
- cy.get("[id='state'] > div > button").click();
- cy.get("div").contains("Kerala").click();
- cy.get("[id='district'] > div > button").click();
- cy.get("div").contains("Ernakulam").click();
- cy.get("[id='local_body'] > div > button").click();
- cy.get("div").contains("Aikaranad").click();
- cy.intercept(/\/api\/v1\/facility/).as("facility");
- cy.get("[name='facilities']")
- .click()
- .type("Dummy Facility 1")
- .wait("@facility");
- cy.get("li[role='option']").first().click();
- cy.get("input[type='checkbox']").click();
- cy.get("[name='phone_number']").type(phone_number);
- cy.get("[name='alt_phone_number']").type(alt_phone_number);
- cy.intercept(/users/).as("check_availability");
- cy.get("#date_of_birth").should("be.visible").click();
- cy.get("#date-input").click().type("25081999");
- cy.get("[name='username']").type(username);
- cy.wait("@check_availability").its("response.statusCode").should("eq", 200);
- cy.get("[name='password']").type("#@Cypress_test123");
- cy.get("[name='c_password']").type("#@Cypress_test123");
- cy.get("[name='first_name']").type("Cypress Test");
- cy.get("[name='last_name']").type("Tester");
- cy.get("[name='email']").type("cypress@tester.com");
- cy.get("[id='gender'] > div > button").click();
- cy.get("div").contains("Male").click();
- cy.get("button[id='submit']").contains("Save User").click();
- cy.verifyNotification("User added successfully");
- });
-
- it("view user and verify details", () => {
- cy.contains("Advanced Filters").click();
- cy.get("[name='first_name']").type("Cypress Test");
- cy.get("[name='last_name']").type("Tester");
- cy.get("#role button").click();
- cy.contains("#role li", "Ward Admin").click();
- cy.get("input[name='district']").click();
- cy.get("input[name='district']").type("Ernakulam");
- cy.get("li[id^='headlessui-combobox-option']")
- .contains("Ernakulam")
- .click();
- cy.get("[placeholder='Phone Number']").click();
- cy.get("[placeholder='Phone Number']").type(phone_number);
- cy.get("[placeholder='WhatsApp Phone Number']").type(alt_phone_number);
- cy.contains("Apply").click();
- cy.intercept(/\/api\/v1\/users/).as("getUsers");
- cy.wait(1000);
- cy.get("[name='username']").type(username);
- cy.wait("@getUsers");
- cy.get("dd[id='count']").contains(/^1$/).click();
- cy.get("div[id='usr_0']").within(() => {
- cy.intercept(`/api/v1/users/${username}/get_facilities/`).as(
- "userFacility"
- );
- cy.get("div[id='role']").contains(/^WardAdmin$/);
- cy.get("div[id='name']").contains("Cypress Test Tester");
- cy.get("div[id='district']").contains(/^Ernakulam$/);
- cy.get("div[id='local_body']").contains("Aikaranad");
- cy.get("div[id='created_by']").contains(/^devdistrictadmin$/);
- cy.get("div[id='home_facility']").contains("No Home Facility");
- cy.get("button[id='facilities']").click();
- cy.wait("@userFacility")
- .getAttached("div[id=facility_0] > div > span")
- .contains("Dummy Facility 1");
- });
- });
-
- it("link facility for user", () => {
- cy.contains("Linked Facilities").click();
- cy.intercept(/\/api\/v1\/facility/).as("getFacilities");
- cy.get("[name='facility']")
- .click()
- .type("Dummy Facility 1")
- .wait("@getFacilities");
- cy.get("li[role='option']").first().click();
- cy.intercept(/\/api\/v1\/users\/\w+\/add_facility\//).as("addFacility");
- cy.get("button[id='link-facility']").click();
- cy.wait("@addFacility")
- // .its("response.statusCode")
- // .should("eq", 201)
- .get("span")
- .contains("Facility - User Already has permission to this facility");
- });
-
- afterEach(() => {
- cy.saveLocalStorage();
- });
-});
-
-describe("Edit User Profile & Error Validation", () => {
- before(() => {
- cy.loginByApi(username, "#@Cypress_test123");
- cy.saveLocalStorage();
- });
-
- beforeEach(() => {
- cy.restoreLocalStorage();
- cy.awaitUrl("/user/profile");
- cy.contains("button", "Edit User Profile").click();
- });
-
- it("First name Field Updation " + username, () => {
- cy.get("input[name=firstName]").clear();
- cy.contains("button[type='submit']", "Update").click();
- cy.get("span.error-text").should("contain", "Field is required");
- cy.get("input[name=firstName]").type("firstName updated");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- it("Last name Field Updation " + username, () => {
- cy.get("input[name=lastName]").clear();
- cy.contains("button[type='submit']", "Update").click();
- cy.get("span.error-text").should("contain", "Field is required");
- cy.get("input[name=lastName]").type("lastName updated");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- it("Age Field Updation " + username, () => {
- cy.get("input[name=age]").clear();
- cy.contains("button[type='submit']", "Update").click();
- cy.get("span.error-text").should("contain", "This field is required");
- cy.get("input[name=age]").type("11");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- it("Phone number Field Updation " + username, () => {
- cy.get("input[name=phoneNumber]").clear();
- cy.contains("button[type='submit']", "Update").click();
- cy.get("span.error-text").should(
- "contain",
- "Please enter valid phone number"
- );
- cy.get("input[name=phoneNumber]").type("+919999999999");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- it("Whatsapp number Field Updation " + username, () => {
- cy.get("input[name=altPhoneNumber]").clear();
- cy.get("input[name=altPhoneNumber]").type("+919999999999");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- it("Email Field Updation " + username, () => {
- cy.get("input[name=email]").clear();
- cy.contains("button[type='submit']", "Update").click();
- cy.get("span.error-text").should("contain", "This field is required");
- cy.get("input[name=email]").type("test@test.com");
- cy.contains("button[type='submit']", "Update").click();
- });
-
- afterEach(() => {
- cy.saveLocalStorage();
- });
-});
-
-// describe("Delete User", () => { district admin wont be able to delete user
-// it("deletes user", () => {
-// cy.loginByApi("devdistrictadmin", "Coronasafe@123");
-// cy.awaitUrl("/user");
-// cy.get("[name='username']").type(username);
-// cy.get("button")
-// .should("contain", "Delete")
-// .contains("Delete")
-// .click();
-// cy.get("button.font-medium.btn.btn-danger").click();
-// });
-// });
diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts
new file mode 100644
index 00000000000..32127ffcb90
--- /dev/null
+++ b/cypress/pageobject/Users/UserCreation.ts
@@ -0,0 +1,79 @@
+// UserCreation.ts
+export class UserCreationPage {
+ clickElementById(elementId: string) {
+ cy.get("#" + elementId).click();
+ }
+
+ typeIntoElementById(elementId: string, value: string) {
+ cy.get("#" + elementId)
+ .click()
+ .type(value);
+ }
+
+ typeIntoElementByIdPostClear(elementId: string, value: string) {
+ cy.get("#" + elementId)
+ .click()
+ .clear()
+ .click()
+ .type(value);
+ }
+
+ clearIntoElementById(elementId: string) {
+ cy.get("#" + elementId)
+ .click()
+ .clear();
+ }
+
+ typeIntoInputByName(inputName: string, value: string) {
+ cy.get("input[name='" + inputName + "']")
+ .click()
+ .type(value);
+ }
+
+ selectOptionContainingText(text: string) {
+ cy.get("[role='option']").contains(text).click();
+ }
+
+ verifyNotification(message: string) {
+ cy.verifyNotification(message);
+ }
+
+ selectFacility(name: string) {
+ this.typeIntoInputByName("facilities", name);
+ this.selectOptionContainingText(name);
+ }
+
+ selectHomeFacility(name: string) {
+ this.clickElementById("home_facility");
+ this.selectOptionContainingText(name);
+ }
+
+ setInputDate(
+ dateElementId: string,
+ inputElementId: string,
+ dateValue: string
+ ) {
+ this.clickElementById(dateElementId);
+ this.typeIntoElementById(inputElementId, dateValue);
+ }
+
+ selectDropdownOption(dropdownId: string, optionText: string) {
+ this.clickElementById(dropdownId);
+ this.selectOptionContainingText(optionText);
+ }
+
+ verifyElementContainsText(elementId: string, expectedText: string) {
+ cy.get("#" + elementId).should("contain.text", expectedText);
+ }
+
+ verifyErrorMessages(errorMessages: string[]) {
+ cy.get(".error-text").then(($errors) => {
+ const displayedErrorMessages = $errors
+ .map((_, el) => Cypress.$(el).text())
+ .get();
+ errorMessages.forEach((errorMessage) => {
+ expect(displayedErrorMessages).to.include(errorMessage);
+ });
+ });
+ }
+}
diff --git a/src/Common/hooks/useMSEplayer.ts b/src/Common/hooks/useMSEplayer.ts
index fcbf216ed6a..4d1bb36b9ac 100644
--- a/src/Common/hooks/useMSEplayer.ts
+++ b/src/Common/hooks/useMSEplayer.ts
@@ -20,6 +20,8 @@ interface UseMSEMediaPlayerOption {
export interface ICameraAssetState {
id: string;
accessKey: string;
+ middleware_address: string;
+ location_middleware: string;
}
export enum StreamStatus {
diff --git a/src/Components/Assets/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx
index 4a3e475419e..44d4d372d73 100644
--- a/src/Components/Assets/AssetType/ONVIFCamera.tsx
+++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx
@@ -53,6 +53,11 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
}
}, [facility, facilityId]);
+ const fallbackMiddleware =
+ asset?.location_object?.middleware_address || facilityMiddlewareHostname;
+
+ const currentMiddleware = middlewareHostname || fallbackMiddleware;
+
useEffect(() => {
if (asset) {
setAssetType(asset?.asset_class);
@@ -105,7 +110,7 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
try {
setLoadingAddPreset(true);
const presetData = await axios.get(
- `https://${facilityMiddlewareHostname}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}`
+ `https://${currentMiddleware}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}`
);
const { res } = await request(routes.createAssetBed, {
@@ -136,9 +141,6 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
};
if (isLoading || loading || !facility) return ;
- const fallbackMiddleware =
- asset?.location_object?.middleware_address || facilityMiddlewareHostname;
-
return (
{["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) && (
@@ -223,7 +225,7 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
addPreset={addPreset}
isLoading={loadingAddPreset}
refreshPresetsHash={refreshPresetsHash}
- facilityMiddlewareHostname={facilityMiddlewareHostname}
+ facilityMiddlewareHostname={currentMiddleware}
/>
) : null}
diff --git a/src/Components/Common/FacilitySelect.tsx b/src/Components/Common/FacilitySelect.tsx
index 400ce115bc5..19494081aa3 100644
--- a/src/Components/Common/FacilitySelect.tsx
+++ b/src/Components/Common/FacilitySelect.tsx
@@ -6,6 +6,7 @@ import { FacilityModel } from "../Facility/models";
interface FacilitySelectProps {
name: string;
+ exclude_user: string;
errors?: string | undefined;
className?: string;
searchAll?: boolean;
@@ -22,6 +23,7 @@ interface FacilitySelectProps {
export const FacilitySelect = (props: FacilitySelectProps) => {
const {
name,
+ exclude_user,
multiple,
selected,
setSelected,
@@ -45,6 +47,7 @@ export const FacilitySelect = (props: FacilitySelectProps) => {
search_text: text,
all: searchAll,
facility_type: facilityType,
+ exclude_user: exclude_user,
district,
};
diff --git a/src/Components/Common/LocationSelect.tsx b/src/Components/Common/LocationSelect.tsx
index a4ad5f284ea..ef7280f1907 100644
--- a/src/Components/Common/LocationSelect.tsx
+++ b/src/Components/Common/LocationSelect.tsx
@@ -5,6 +5,7 @@ import AutocompleteFormField from "../Form/FormFields/Autocomplete";
import AutocompleteMultiSelectFormField from "../Form/FormFields/AutocompleteMultiselect";
interface LocationSelectProps {
name: string;
+ disabled?: boolean;
margin?: string;
errors: string;
className?: string;
@@ -26,6 +27,7 @@ export const LocationSelect = (props: LocationSelectProps) => {
errors,
className = "",
facilityId,
+ disabled = false,
} = props;
const [locations, setLocations] = useState<{ name: string; id: string }[]>(
[]
@@ -40,6 +42,7 @@ export const LocationSelect = (props: LocationSelectProps) => {
};
useEffect(() => {
+ if (!facilityId) return;
const params = {
limit: 14,
search_text: query,
@@ -56,6 +59,7 @@ export const LocationSelect = (props: LocationSelectProps) => {
return props.multiple ? (
handleValueChange(value as unknown as string[])}
@@ -72,6 +76,7 @@ export const LocationSelect = (props: LocationSelectProps) => {
) : (
handleValueChange([value])}
diff --git a/src/Components/Common/Sidebar/SidebarUserCard.tsx b/src/Components/Common/Sidebar/SidebarUserCard.tsx
index 6f243d430d0..59970e8a73c 100644
--- a/src/Components/Common/Sidebar/SidebarUserCard.tsx
+++ b/src/Components/Common/Sidebar/SidebarUserCard.tsx
@@ -37,6 +37,7 @@ const SidebarUserCard = ({ shrinked }: { shrinked: boolean }) => {
{profileName}
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx
index 2acce781b81..ac39aac9df6 100644
--- a/src/Components/Facility/ConsultationDetails/index.tsx
+++ b/src/Components/Facility/ConsultationDetails/index.tsx
@@ -271,7 +271,7 @@ export const ConsultationDetails = (props: any) => {
/>
-
{value.length !== 0 && (
diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx
index 61b18faa367..7a78d94582e 100644
--- a/src/Components/Patient/ManagePatients.tsx
+++ b/src/Components/Patient/ManagePatients.tsx
@@ -16,9 +16,14 @@ import {
getAllPatient,
getAnyFacility,
getDistrict,
+ getFacilityAssetLocation,
getLocalBody,
} from "../../Redux/actions";
-import { statusType, useAbortableEffect } from "../../Common/utils";
+import {
+ statusType,
+ useAbortableEffect,
+ parseOptionId,
+} from "../../Common/utils";
import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover";
import ButtonV2 from "../Common/components/ButtonV2";
@@ -36,7 +41,6 @@ import RecordMeta from "../../CAREUI/display/RecordMeta";
import SearchInput from "../Form/SearchInput";
import SortDropdownMenu from "../Common/SortDropdown";
import SwitchTabs from "../Common/components/SwitchTabs";
-import { parseOptionId } from "../../Common/utils";
import { formatAge, parsePhoneNumber } from "../../Utils/utils.js";
import { useDispatch } from "react-redux";
import useFilters from "../../Common/hooks/useFilters";
@@ -104,6 +108,7 @@ export const PatientManager = () => {
const [districtName, setDistrictName] = useState("");
const [localbodyName, setLocalbodyName] = useState("");
const [facilityBadgeName, setFacilityBadge] = useState("");
+ const [locationBadgeName, setLocationBadge] = useState("");
const [phone_number, setPhoneNumber] = useState("");
const [phoneNumberError, setPhoneNumberError] = useState("");
const [emergency_phone_number, setEmergencyPhoneNumber] = useState("");
@@ -199,6 +204,8 @@ export const PatientManager = () => {
qParams.last_consultation_admitted_bed_type_list || undefined,
last_consultation_discharge_reason:
qParams.last_consultation_discharge_reason || undefined,
+ last_consultation_current_bed__location:
+ qParams.last_consultation_current_bed__location || undefined,
srf_id: qParams.srf_id || undefined,
number_of_doses: qParams.number_of_doses || undefined,
covin_id: qParams.covin_id || undefined,
@@ -344,6 +351,7 @@ export const PatientManager = () => {
qParams.age_min,
qParams.last_consultation_admitted_bed_type_list,
qParams.last_consultation_discharge_reason,
+ qParams.last_consultation_current_bed__location,
qParams.facility,
qParams.facility_type,
qParams.district,
@@ -443,12 +451,32 @@ export const PatientManager = () => {
[dispatch, qParams.facility]
);
+ const fetchLocationBadgeName = useCallback(
+ async (status: statusType) => {
+ const res =
+ qParams.last_consultation_current_bed__location &&
+ (await dispatch(
+ getFacilityAssetLocation(
+ qParams.facility,
+ qParams.last_consultation_current_bed__location
+ )
+ ));
+
+ if (!status.aborted) {
+ setLocationBadge(res?.data?.name);
+ }
+ },
+ [dispatch, qParams.last_consultation_current_bed__location]
+ );
+
useAbortableEffect(
(status: statusType) => {
fetchFacilityBadgeName(status);
+ fetchLocationBadgeName(status);
},
- [fetchFacilityBadgeName]
+ [fetchFacilityBadgeName, fetchLocationBadgeName]
);
+
const LastAdmittedToTypeBadges = () => {
const badge = (key: string, value: any, id: string) => {
return (
@@ -519,27 +547,23 @@ export const PatientManager = () => {
{patient?.last_consultation?.current_bed &&
patient?.last_consultation?.discharge_date === null ? (
-
-
+
+
{
patient?.last_consultation?.current_bed?.bed_object
?.location_object?.name
}
-
- {
- patient?.last_consultation?.current_bed?.bed_object
- ?.location_object?.name
- }
-
- {patient?.last_consultation?.current_bed?.bed_object.name}
-
- {
- patient?.last_consultation?.current_bed?.bed_object
- ?.name
- }
-
+ {patient?.last_consultation?.current_bed?.bed_object?.name}
+
+
+ {
+ patient?.last_consultation?.current_bed?.bed_object
+ ?.location_object?.name
+ }
+
+ {patient?.last_consultation?.current_bed?.bed_object?.name}
) : patient.last_consultation?.suggestion === "DC" ? (
@@ -940,6 +964,11 @@ export const PatientManager = () => {
"last_consultation_medico_legal_case"
),
value("Facility", "facility", facilityBadgeName),
+ value(
+ "Location",
+ "last_consultation_current_bed__location",
+ locationBadgeName
+ ),
badge("Facility Type", "facility_type"),
value("District", "district", districtName),
ordering(),
diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx
index 97f5a6c6b9c..7bc7f7b861f 100644
--- a/src/Components/Patient/PatientFilter.tsx
+++ b/src/Components/Patient/PatientFilter.tsx
@@ -34,6 +34,7 @@ import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover";
import AccordionV2 from "../Common/components/AccordionV2";
import { dateQueryString } from "../../Utils/utils";
import dayjs from "dayjs";
+import { LocationSelect } from "../Common/LocationSelect";
const getDate = (value: any) =>
value && dayjs(value).isValid() && dayjs(value).toDate();
@@ -79,6 +80,8 @@ export default function PatientFilter(props: any) {
filter.last_consultation_admitted_bed_type_list
? filter.last_consultation_admitted_bed_type_list.split(",")
: [],
+ last_consultation_current_bed__location:
+ filter.last_consultation_current_bed__location || "",
last_consultation_discharge_reason:
filter.last_consultation_discharge_reason || null,
srf_id: filter.srf_id || null,
@@ -128,6 +131,7 @@ export default function PatientFilter(props: any) {
last_consultation_discharge_date_before: "",
last_consultation_discharge_date_after: "",
last_consultation_admitted_to_list: [],
+ last_consultation_current_bed__location: "",
srf_id: "",
number_of_doses: null,
covin_id: "",
@@ -239,6 +243,7 @@ export default function PatientFilter(props: any) {
last_consultation_discharge_date_after,
last_consultation_admitted_bed_type_list,
last_consultation_discharge_reason,
+ last_consultation_current_bed__location,
number_of_doses,
covin_id,
srf_id,
@@ -256,6 +261,8 @@ export default function PatientFilter(props: any) {
district: district || "",
lsgBody: lsgBody || "",
facility: facility || "",
+ last_consultation_current_bed__location:
+ last_consultation_current_bed__location || "",
facility_type: facility_type || "",
date_declared_positive_before: dateQueryString(
date_declared_positive_before
@@ -588,13 +595,29 @@ export default function PatientFilter(props: any) {
setFacility(obj, "facility")}
/>
-
+ Location
+
+ setFilterState({
+ ...filterState,
+ last_consultation_current_bed__location: selected,
+ })
+ }
+ />
+
+
Facility type
{
close();
setShowABHAProfile(true);
+ triggerGoal("Patient Card Button Clicked", {
+ buttonName: "Show ABHA Profile",
+ consultationId: consultation?.id,
+ userId: authUser?.id,
+ });
}}
>
@@ -497,6 +510,11 @@ export default function PatientInfoCard(props: {
{
+ triggerGoal("Patient Card Button Clicked", {
+ buttonName: "Link Care Context",
+ consultationId: consultation?.id,
+ userId: authUser?.id,
+ });
close();
setShowLinkCareContext(true);
}}
@@ -532,6 +550,11 @@ export default function PatientInfoCard(props: {
{
+ triggerGoal("Patient Card Button Clicked", {
+ buttonName: "Medico Legal Case",
+ consultationId: consultation?.id,
+ userId: authUser?.id,
+ });
setMedicoLegalCase(checked);
switchMedicoLegalCase(checked);
}}
diff --git a/src/Components/Resource/BadgesList.tsx b/src/Components/Resource/BadgesList.tsx
index 31b8f1c17ae..6977861d596 100644
--- a/src/Components/Resource/BadgesList.tsx
+++ b/src/Components/Resource/BadgesList.tsx
@@ -1,63 +1,25 @@
-import { useState, useEffect } from "react";
-import { getAnyFacility } from "../../Redux/actions";
-import { useDispatch } from "react-redux";
import { SHIFTING_FILTER_ORDER } from "../../Common/constants";
+import routes from "../../Redux/api";
+import useQuery from "../../Utils/request/useQuery";
+
+function useFacilityQuery(facilityId: string | undefined) {
+ return useQuery(routes.getAnyFacility, {
+ pathParams: { id: String(facilityId) },
+ prefetch: facilityId !== undefined,
+ });
+}
export default function BadgesList(props: any) {
const { appliedFilters, FilterBadges } = props;
- const [orginFacilityName, setOrginFacilityName] = useState("");
- const [approvingFacilityName, setApprovingFacilityName] = useState("");
- const [assignedFacilityName, setAssignedFacilityName] = useState("");
- const dispatch: any = useDispatch();
-
- useEffect(() => {
- async function fetchData() {
- if (!appliedFilters.origin_facility) return setOrginFacilityName("");
- const res = await dispatch(
- getAnyFacility(appliedFilters.origin_facility, "origin_facility_name")
- );
- setOrginFacilityName(res?.data?.name);
- }
- fetchData();
- }, [dispatch, appliedFilters.origin_facility]);
-
- useEffect(() => {
- async function fetchData() {
- if (!appliedFilters.approving_facility)
- return setApprovingFacilityName("");
- const res = await dispatch(
- getAnyFacility(
- appliedFilters.approving_facility,
- "approving_facility_name"
- )
- );
- setApprovingFacilityName(res?.data?.name);
- }
- fetchData();
- }, [dispatch, appliedFilters.approving_facility]);
-
- useEffect(() => {
- async function fetchData() {
- if (!appliedFilters.assigned_facility) return setAssignedFacilityName("");
- const res = await dispatch(
- getAnyFacility(
- appliedFilters.assigned_facility,
- "assigned_facility_name"
- )
- );
- setAssignedFacilityName(res?.data?.name);
- }
- fetchData();
- }, [dispatch, appliedFilters.assigned_facility]);
+ const originFacility = useFacilityQuery(appliedFilters.origin_facility);
+ const approvingFacility = useFacilityQuery(appliedFilters.approving_facility);
+ const assignedFacility = useFacilityQuery(appliedFilters.assigned_facility);
const getDescShiftingFilterOrder = (ordering: any) => {
- let desc = "";
- SHIFTING_FILTER_ORDER.map((item: any) => {
- if (item.text === ordering) {
- desc = item.desc;
- }
- });
- return desc;
+ const foundItem = SHIFTING_FILTER_ORDER.find(
+ (item) => item.text === ordering
+ );
+ return foundItem ? foundItem.desc : "";
};
return (
@@ -75,13 +37,25 @@ export default function BadgesList(props: any) {
}),
...dateRange("Modified", "modified_date"),
...dateRange("Created", "created_date"),
- value("Origin facility", "origin_facility", orginFacilityName),
+ value(
+ "Origin facility",
+ "origin_facility",
+ appliedFilters.origin_facility ? originFacility?.data?.name || "" : ""
+ ),
value(
"Approving facility",
"approving_facility",
- approvingFacilityName
+ appliedFilters.approving_facility
+ ? approvingFacility?.data?.name || ""
+ : ""
+ ),
+ value(
+ "Assigned facility",
+ "assigned_facility",
+ appliedFilters.assigned_facility
+ ? assignedFacility?.data?.name || ""
+ : ""
),
- value("Assigned facility", "assigned_facility", assignedFacilityName),
]}
/>
);
diff --git a/src/Components/Resource/CommentSection.tsx b/src/Components/Resource/CommentSection.tsx
index 01bd67454f5..25d8142dae7 100644
--- a/src/Components/Resource/CommentSection.tsx
+++ b/src/Components/Resource/CommentSection.tsx
@@ -1,60 +1,26 @@
-import { useCallback, useState } from "react";
-import { useDispatch } from "react-redux";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-import { getResourceComments, addResourceComments } from "../../Redux/actions";
+import { useState } from "react";
import * as Notification from "../../Utils/Notifications.js";
-import Pagination from "../Common/Pagination";
import { formatDateTime } from "../../Utils/utils";
import CircularProgress from "../Common/components/CircularProgress";
import ButtonV2 from "../Common/components/ButtonV2";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
+import PaginatedList from "../../CAREUI/misc/PaginatedList";
+import { IComment } from "./models";
+import request from "../../Utils/request/request";
-interface CommentSectionProps {
- id: string;
-}
-const CommentSection = (props: CommentSectionProps) => {
- const dispatch: any = useDispatch();
- const initialData: any = [];
- const [comments, setComments] = useState(initialData);
+const CommentSection = (props: { id: string }) => {
const [commentBox, setCommentBox] = useState("");
- const [isLoading, setIsLoading] = useState(true);
-
- const [currentPage, setCurrentPage] = useState(1);
- const [totalCount, setTotalCount] = useState(0);
- const [offset, setOffset] = useState(0);
- const limit = 8;
-
- const handlePagination = (page: number, limit: number) => {
- const offset = (page - 1) * limit;
- setCurrentPage(page);
- setOffset(offset);
- };
-
- const fetchData = useCallback(
- async (status: statusType = { aborted: false }) => {
- setIsLoading(true);
- const res = await dispatch(
- getResourceComments(props.id, { limit, offset })
- );
- if (!status.aborted) {
- if (res && res.data) {
- setComments(res.data?.results);
- setTotalCount(res.data.count);
- }
- setIsLoading(false);
- }
- },
- [props.id, dispatch, offset]
- );
-
- useAbortableEffect(
- (status: statusType) => {
- fetchData(status);
- },
- [fetchData]
+ const { loading, refetch: resourceRefetch } = useQuery(
+ routes.getResourceComments,
+ {
+ pathParams: { id: props.id },
+ query: { limit: 8, offset: 0 },
+ }
);
- const onSubmitComment = () => {
+ const onSubmitComment = async () => {
const payload = {
comment: commentBox,
};
@@ -64,13 +30,16 @@ const CommentSection = (props: CommentSectionProps) => {
});
return;
}
- dispatch(addResourceComments(props.id, payload)).then((_: any) => {
- Notification.Success({ msg: "Comment added successfully" });
- fetchData();
+ const { res } = await request(routes.addResourceComments, {
+ pathParams: { id: props.id },
+ body: payload,
});
+ if (res?.ok) {
+ Notification.Success({ msg: "Comment added successfully" });
+ resourceRefetch();
+ }
setCommentBox("");
};
-
return (
{
Post Your Comment
- {isLoading ? (
+ {loading ? (
) : (
- comments.map((comment: any) => (
-
-
-
-
- {formatDateTime(comment.modified_date) || "-"}
-
-
-
-
- {comment.created_by_object?.first_name?.charAt(0) || "U"}
+
+ {() => (
+
+
+ No comments available
+
+
+
+
+
>
+ {(item) => }
+
+
-
- {comment.created_by_object?.first_name || "Unknown"}{" "}
- {comment.created_by_object?.last_name}
-
-
- ))
+ )}
+
)}
- {totalCount > limit && (
-
- )}
);
};
export default CommentSection;
+
+export const Comment = ({
+ comment,
+ created_by_object,
+ modified_date,
+}: IComment) => (
+
+
+
+
+ {formatDateTime(modified_date) || "-"}
+
+
+
+
+ {created_by_object?.first_name?.charAt(0) || "U"}
+
+
+ {created_by_object?.first_name || "Unknown"}{" "}
+ {created_by_object?.last_name}
+
+
+
+);
diff --git a/src/Components/Resource/ListFilter.tsx b/src/Components/Resource/ListFilter.tsx
index 7a47732db64..afe48eedfd0 100644
--- a/src/Components/Resource/ListFilter.tsx
+++ b/src/Components/Resource/ListFilter.tsx
@@ -1,8 +1,5 @@
-import { useEffect, useState } from "react";
import { FacilitySelect } from "../Common/FacilitySelect";
import { RESOURCE_FILTER_ORDER } from "../../Common/constants";
-import { getAnyFacility } from "../../Redux/actions";
-import { useDispatch } from "react-redux";
import { RESOURCE_CHOICES } from "../../Common/constants";
import useMergeState from "../../Common/hooks/useMergeState";
import { navigate } from "raviger";
@@ -15,6 +12,8 @@ import { DateRange } from "../Common/DateRangeInputV2";
import DateRangeFormField from "../Form/FormFields/DateRangeFormField";
import dayjs from "dayjs";
import { dateQueryString } from "../../Utils/utils";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
const clearFilterState = {
origin_facility: "",
@@ -37,9 +36,6 @@ const getDate = (value: any) =>
export default function ListFilter(props: any) {
const { filter, onChange, closeFilter } = props;
- const [isOriginLoading, setOriginLoading] = useState(false);
- const [isResourceLoading, setResourceLoading] = useState(false);
- const [isAssignedLoading, setAssignedLoading] = useState(false);
const [filterState, setFilterState] = useMergeState({
origin_facility: filter.origin_facility || "",
origin_facility_ref: null,
@@ -55,55 +51,42 @@ export default function ListFilter(props: any) {
ordering: filter.ordering || null,
status: filter.status || null,
});
- const dispatch: any = useDispatch();
- useEffect(() => {
- async function fetchData() {
- if (filter.origin_facility) {
- setOriginLoading(true);
- const res = await dispatch(
- getAnyFacility(filter.origin_facility, "origin_facility")
- );
- if (res && res.data) {
- setFilterState({ origin_facility_ref: res.data });
- }
- setOriginLoading(false);
+ const { loading: orginFacilityLoading } = useQuery(routes.getAnyFacility, {
+ prefetch: filter.origin_facility !== undefined,
+ pathParams: { id: filter.origin_facility },
+ onResponse: ({ res, data }) => {
+ if (res && data) {
+ setFilterState({
+ origin_facility_ref: filter.origin_facility === "" ? "" : data,
+ });
}
- }
- fetchData();
- }, [dispatch]);
+ },
+ });
- useEffect(() => {
- async function fetchData() {
- if (filter.approving_facility) {
- setResourceLoading(true);
- const res = await dispatch(
- getAnyFacility(filter.approving_facility, "approving_facility")
- );
- if (res && res.data) {
- setFilterState({ approving_facility_ref: res.data });
- }
- setResourceLoading(false);
+ const { loading: resourceFacilityLoading } = useQuery(routes.getAnyFacility, {
+ prefetch: filter.approving_facility !== undefined,
+ pathParams: { id: filter.approving_facility },
+ onResponse: ({ res, data }) => {
+ if (res && data) {
+ setFilterState({
+ approving_facility_ref: filter.approving_facility === "" ? "" : data,
+ });
}
- }
- fetchData();
- }, [dispatch]);
+ },
+ });
- useEffect(() => {
- async function fetchData() {
- if (filter.assigned_facility) {
- setAssignedLoading(true);
- const res = await dispatch(
- getAnyFacility(filter.assigned_facility, "assigned_facility")
- );
- if (res && res.data) {
- setFilterState({ assigned_facility_ref: res.data });
- }
- setAssignedLoading(false);
+ const { loading: assignedFacilityLoading } = useQuery(routes.getAnyFacility, {
+ pathParams: { id: filter.assigned_facility },
+ prefetch: filter.assigned_facility !== undefined,
+ onResponse: ({ res, data }) => {
+ if (res && data) {
+ setFilterState({
+ assigned_facility_ref: filter.assigned_facility === "" ? "" : data,
+ });
}
- }
- fetchData();
- }, [dispatch]);
+ },
+ });
const setFacility = (selected: any, name: string) => {
setFilterState({
@@ -178,7 +161,7 @@ export default function ListFilter(props: any) {
Origin facility
- {isOriginLoading ? (
+ {orginFacilityLoading && filter.origin_facility ? (
) : (
Resource approving facility
- {isResourceLoading ? (
+ {filter.approving_facility && resourceFacilityLoading ? (
) : (
Assigned facility
- {isAssignedLoading ? (
+ {filter.approving_facility && assignedFacilityLoading ? (
) : (
import("../Common/Loading"));
const PageTitle = lazy(() => import("../Common/PageTitle"));
export default function ListView() {
- const dispatch: any = useDispatch();
const { qParams, Pagination, FilterBadges, advancedFilter, resultsPerPage } =
useFilters({});
- const [data, setData] = useState([]);
- const [totalCount, setTotalCount] = useState(0);
- const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const onBoardViewBtnClick = () =>
navigate("/resource/board", { query: qParams });
const appliedFilters = formatFilter(qParams);
- const refreshList = () => {
- fetchData();
- };
-
- const fetchData = () => {
- setIsLoading(true);
- dispatch(
- listResourceRequests(
- formatFilter({
- ...qParams,
- offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage,
- }),
- "resource-list-call"
- )
- ).then((res: any) => {
- if (res && res.data) {
- setData(res.data.results);
- setTotalCount(res.data.count);
- }
- setIsLoading(false);
- });
- };
-
- useEffect(() => {
- fetchData();
- }, [
- qParams.status,
- qParams.facility,
- qParams.origin_facility,
- qParams.approving_facility,
- qParams.assigned_facility,
- qParams.emergency,
- qParams.created_date_before,
- qParams.created_date_after,
- qParams.modified_date_before,
- qParams.modified_date_after,
- qParams.ordering,
- qParams.page,
- ]);
+ const { loading, data, refetch } = useQuery(routes.listResourceRequests, {
+ query: formatFilter({
+ ...qParams,
+ offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage,
+ }),
+ });
const showResourceCardList = (data: any) => {
if (data && !data.length) {
@@ -218,14 +178,14 @@ export default function ListView() {
- {isLoading ? (
+ {loading ? (
) : (
- {showResourceCardList(data)}
+ {data?.results && showResourceCardList(data?.results)}
-
+
)}
diff --git a/src/Components/Resource/ResourceBoard.tsx b/src/Components/Resource/ResourceBoard.tsx
index 66bf559be0d..217f2941e10 100644
--- a/src/Components/Resource/ResourceBoard.tsx
+++ b/src/Components/Resource/ResourceBoard.tsx
@@ -1,15 +1,13 @@
import { useState, useEffect } from "react";
-import { useDispatch } from "react-redux";
-import {
- listResourceRequests,
- downloadResourceRequests,
-} from "../../Redux/actions";
+import { downloadResourceRequests } from "../../Redux/actions";
import { navigate } from "raviger";
import { classNames } from "../../Utils/utils";
import { useDrag, useDrop } from "react-dnd";
import { formatDateTime } from "../../Utils/utils";
import { ExportButton } from "../Common/Export";
import dayjs from "../../Utils/dayjs";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
const limit = 14;
@@ -118,7 +116,6 @@ const ResourceCard = ({ resource }: any) => {
-
{resource.assigned_to_object && (
{
)}
-
@@ -273,13 +258,14 @@ export default function ResourceBoard({
- ) : data?.length > 0 ? (
+ ) : data && data?.results.length > 0 ? (
boardFilter(board)
) : (
No requests to show.
)}
{!isLoading.board &&
- data?.length < (totalCount || 0) &&
+ data &&
+ data?.results.length < (data?.count || 0) &&
(isLoading.more ? (
Loading
diff --git a/src/Components/Resource/ResourceCreate.tsx b/src/Components/Resource/ResourceCreate.tsx
index cf9e5f6f22a..d0a6c36272b 100644
--- a/src/Components/Resource/ResourceCreate.tsx
+++ b/src/Components/Resource/ResourceCreate.tsx
@@ -1,8 +1,7 @@
-import { useReducer, useState, useEffect, lazy } from "react";
+import { useReducer, useState, lazy } from "react";
import { FacilitySelect } from "../Common/FacilitySelect";
import * as Notification from "../../Utils/Notifications.js";
-import { useDispatch } from "react-redux";
import { navigate } from "raviger";
import {
OptionsType,
@@ -11,8 +10,6 @@ import {
} from "../../Common/constants";
import { parsePhoneNumber } from "../../Utils/utils";
import { phonePreg } from "../../Common/validation";
-
-import { createResource, getAnyFacility } from "../../Redux/actions";
import { Cancel, Submit } from "../Common/components/ButtonV2";
import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
import { FieldChangeEvent } from "../Form/FormFields/Utils";
@@ -26,6 +23,9 @@ import { FieldLabel } from "../Form/FormFields/FormField";
import Card from "../../CAREUI/display/Card";
import Page from "../Common/components/Page";
import { PhoneNumberValidator } from "../Form/FieldValidators";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
+import request from "../../Utils/request/request";
const Loading = lazy(() => import("../Common/Loading"));
@@ -87,10 +87,7 @@ export default function ResourceCreate(props: resourceProps) {
const { goBack } = useAppHistory();
const { facilityId } = props;
const { t } = useTranslation();
-
- const dispatchAction: any = useDispatch();
const [isLoading, setIsLoading] = useState(false);
- const [facilityName, setFacilityName] = useState("");
const resourceFormReducer = (state = initialState, action: any) => {
switch (action.type) {
@@ -113,18 +110,10 @@ export default function ResourceCreate(props: resourceProps) {
const [state, dispatch] = useReducer(resourceFormReducer, initialState);
- useEffect(() => {
- async function fetchFacilityName() {
- if (facilityId) {
- const res = await dispatchAction(getAnyFacility(facilityId));
-
- setFacilityName(res?.data?.name || "");
- } else {
- setFacilityName("");
- }
- }
- fetchFacilityName();
- }, [dispatchAction, facilityId]);
+ const { data: facilityData } = useQuery(routes.getAnyFacility, {
+ prefetch: facilityId !== undefined,
+ pathParams: { id: String(facilityId) },
+ });
const validateForm = () => {
const errors = { ...initError };
@@ -184,11 +173,11 @@ export default function ResourceCreate(props: resourceProps) {
if (validForm) {
setIsLoading(true);
- const data = {
+ const resourceData = {
status: "PENDING",
category: state.form.category,
sub_category: state.form.sub_category,
- origin_facility: props.facilityId,
+ origin_facility: String(props.facilityId),
approving_facility: (state.form.approving_facility || {}).id,
assigned_facility: (state.form.assigned_facility || {}).id,
emergency: state.form.emergency === "true",
@@ -202,16 +191,18 @@ export default function ResourceCreate(props: resourceProps) {
requested_quantity: state.form.requested_quantity || 0,
};
- const res = await dispatchAction(createResource(data));
+ const { res, data } = await request(routes.createResource, {
+ body: resourceData,
+ });
setIsLoading(false);
- if (res && res.data && (res.status == 201 || res.status == 200)) {
+ if (res?.ok && data) {
await dispatch({ type: "set_form", form: initForm });
Notification.Success({
msg: "Resource request created successfully",
});
- navigate(`/resource/${res.data.id}`);
+ navigate(`/resource/${data.id}`);
}
}
};
@@ -224,7 +215,7 @@ export default function ResourceCreate(props: resourceProps) {
import("../Common/Loading"));
export default function ResourceDetails(props: { id: string }) {
- const dispatch: any = useDispatch();
- const initialData: any = {};
- const [data, setData] = useState(initialData);
- const [isLoading, setIsLoading] = useState(true);
const [isPrintMode, setIsPrintMode] = useState(false);
-
const [openDeleteResourceDialog, setOpenDeleteResourceDialog] =
useState(false);
-
- const fetchData = useCallback(
- async (status: statusType) => {
- setIsLoading(true);
- const res = await dispatch(getResourceDetails({ id: props.id }));
- if (!status.aborted) {
- if (res && res.data) {
- setData(res.data);
- } else {
- navigate("/not-found");
- }
- setIsLoading(false);
+ const { data, loading } = useQuery(routes.getResourceDetails, {
+ pathParams: { id: props.id },
+ onResponse: ({ res, data }) => {
+ if (!res && !data) {
+ navigate("/not-found");
}
},
- [props.id, dispatch]
- );
-
- useAbortableEffect(
- (status: statusType) => {
- fetchData(status);
- },
- [fetchData]
- );
+ });
const handleResourceDelete = async () => {
setOpenDeleteResourceDialog(true);
-
- const res = await dispatch(deleteResourceRecord(props.id));
+ const { res, data } = await request(routes.deleteResourceRecord, {
+ pathParams: { id: props.id },
+ });
if (res?.status === 204) {
Notification.Success({
msg: "Resource record has been deleted successfully.",
});
} else {
Notification.Error({
- msg: "Error while deleting Resource: " + (res?.data?.detail || ""),
+ msg: "Error while deleting Resource: " + (data?.detail || ""),
});
}
@@ -223,7 +203,7 @@ export default function ResourceDetails(props: { id: string }) {
);
};
- if (isLoading) {
+ if (loading || !data) {
return ;
}
diff --git a/src/Components/Resource/ResourceDetailsUpdate.tsx b/src/Components/Resource/ResourceDetailsUpdate.tsx
index ce0751e7b2e..c692618a774 100644
--- a/src/Components/Resource/ResourceDetailsUpdate.tsx
+++ b/src/Components/Resource/ResourceDetailsUpdate.tsx
@@ -1,15 +1,7 @@
import * as Notification from "../../Utils/Notifications.js";
-
import { Cancel, Submit } from "../Common/components/ButtonV2";
-import { lazy, useCallback, useEffect, useReducer, useState } from "react";
-import {
- getResourceDetails,
- getUserList,
- updateResource,
-} from "../../Redux/actions";
+import { lazy, useReducer, useState } from "react";
import { navigate, useQueryParams } from "raviger";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-
import Card from "../../CAREUI/display/Card";
import CircularProgress from "../Common/components/CircularProgress";
import { FacilitySelect } from "../Common/FacilitySelect";
@@ -22,9 +14,11 @@ import { SelectFormField } from "../Form/FormFields/SelectFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import TextFormField from "../Form/FormFields/TextFormField";
import UserAutocompleteFormField from "../Common/UserAutocompleteFormField";
-
import useAppHistory from "../../Common/hooks/useAppHistory";
-import { useDispatch } from "react-redux";
+import useQuery from "../../Utils/request/useQuery.js";
+import routes from "../../Redux/api.js";
+import { UserModel } from "../Users/models.js";
+import request from "../../Utils/request/request.js";
const Loading = lazy(() => import("../Common/Loading"));
@@ -67,13 +61,9 @@ const initialState = {
export const ResourceDetailsUpdate = (props: resourceProps) => {
const { goBack } = useAppHistory();
- const dispatchAction: any = useDispatch();
const [qParams, _] = useQueryParams();
const [isLoading, setIsLoading] = useState(true);
- const [assignedQuantity, setAssignedQuantity] = useState(0);
- const [requestTitle, setRequestTitle] = useState("");
- const [assignedUser, SetAssignedUser] = useState(null);
- const [assignedUserLoading, setAssignedUserLoading] = useState(false);
+ const [assignedUser, SetAssignedUser] = useState();
const resourceFormReducer = (state = initialState, action: any) => {
switch (action.type) {
case "set_form": {
@@ -95,23 +85,13 @@ export const ResourceDetailsUpdate = (props: resourceProps) => {
const [state, dispatch] = useReducer(resourceFormReducer, initialState);
- useEffect(() => {
- async function fetchData() {
- if (state.form.assigned_to) {
- setAssignedUserLoading(true);
-
- const res = await dispatchAction(
- getUserList({ id: state.form.assigned_to })
- );
-
- if (res && res.data && res.data.count)
- SetAssignedUser(res.data.results[0]);
-
- setAssignedUserLoading(false);
+ const { loading: assignedUserLoading } = useQuery(routes.userList, {
+ onResponse: ({ res, data }) => {
+ if (res?.ok && data && data.count) {
+ SetAssignedUser(data.results[0]);
}
- }
- fetchData();
- }, [dispatchAction, state.form.assigned_to]);
+ },
+ });
const validateForm = () => {
const errors = { ...initError };
@@ -147,13 +127,25 @@ export const ResourceDetailsUpdate = (props: resourceProps) => {
dispatch({ type: "set_form", form });
};
+ const { data: resourceDetails } = useQuery(routes.getResourceDetails, {
+ pathParams: { id: props.id },
+ onResponse: ({ res, data }) => {
+ if (res && data) {
+ const d = data;
+ d["status"] = qParams.status || data.status;
+ dispatch({ type: "set_form", form: d });
+ }
+ setIsLoading(false);
+ },
+ });
+
const handleSubmit = async () => {
const validForm = validateForm();
if (validForm) {
setIsLoading(true);
- const data = {
+ const resourceData = {
category: "OXYGEN",
status: state.form.status,
origin_facility: state.form.origin_facility_object?.id,
@@ -167,14 +159,17 @@ export const ResourceDetailsUpdate = (props: resourceProps) => {
assigned_quantity:
state.form.status === "PENDING"
? state.form.assigned_quantity
- : assignedQuantity,
+ : resourceDetails?.assigned_quantity || 0,
};
- const res = await dispatchAction(updateResource(props.id, data));
+ const { res, data } = await request(routes.updateResource, {
+ pathParams: { id: props.id },
+ body: resourceData,
+ });
setIsLoading(false);
- if (res && res.status == 200 && res.data) {
- dispatch({ type: "set_form", form: res.data });
+ if (res && res.status == 200 && data) {
+ dispatch({ type: "set_form", form: data });
Notification.Success({
msg: "Resource request updated successfully",
});
@@ -186,31 +181,6 @@ export const ResourceDetailsUpdate = (props: resourceProps) => {
}
};
- const fetchData = useCallback(
- async (status: statusType) => {
- setIsLoading(true);
- const res = await dispatchAction(getResourceDetails({ id: props.id }));
- if (!status.aborted) {
- if (res && res.data) {
- setRequestTitle(res.data.title);
- setAssignedQuantity(res.data.assigned_quantity);
- const d = res.data;
- d["status"] = qParams.status || res.data.status;
- dispatch({ type: "set_form", form: d });
- }
- setIsLoading(false);
- }
- },
- [props.id, dispatchAction, qParams.status]
- );
-
- useAbortableEffect(
- (status: statusType) => {
- fetchData(status);
- },
- [fetchData]
- );
-
if (isLoading) {
return ;
}
@@ -219,7 +189,7 @@ export const ResourceDetailsUpdate = (props: resourceProps) => {
diff --git a/src/Components/Resource/models.ts b/src/Components/Resource/models.ts
new file mode 100644
index 00000000000..f10ac988552
--- /dev/null
+++ b/src/Components/Resource/models.ts
@@ -0,0 +1,41 @@
+import { PerformedByModel } from "../HCX/misc";
+
+export interface IComment {
+ id: string;
+ created_by_object: PerformedByModel;
+ created_date: string;
+ modified_date: string;
+ comment: string;
+ created_by: number;
+}
+
+export interface IResource {
+ id: string;
+ title: string;
+ emergency: boolean;
+ status?: string;
+ origin_facility_object: {
+ name: string;
+ };
+ approving_facility_object: {
+ name: string;
+ };
+ assigned_facility_object: {
+ name: string;
+ };
+ assigned_quantity: number;
+ modified_date: string;
+ category: any;
+ sub_category: number;
+ origin_facility: string;
+ approving_facility: string;
+ assigned_facility: string;
+ reason: string;
+ refering_facility_contact_name: string;
+ refering_facility_contact_number: string;
+ requested_quantity: number;
+ assigned_to_object: PerformedByModel;
+ created_by_object: PerformedByModel;
+ created_date: string;
+ last_edited_by_object: PerformedByModel;
+}
diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx
index 150c102aff1..1771a6ca22f 100644
--- a/src/Components/Users/ManageUsers.tsx
+++ b/src/Components/Users/ManageUsers.tsx
@@ -134,7 +134,11 @@ export default function ManageUsers() {
);
const addUser = (
- navigate("/users/add")}>
+ navigate("/users/add")}
+ >
Add New User
@@ -702,6 +706,7 @@ function UserFacilities(props: { user: any }) {
-
setShowEdit(!showEdit)} type="button">
+ setShowEdit(!showEdit)}
+ type="button"
+ id="edit-cancel-profile-button"
+ >
{showEdit ? "Cancel" : "Edit User Profile"}
handleSignOut(true)}>
@@ -431,7 +435,10 @@ export default function UserProfile() {
{!showEdit && (
-
+
-
Username
@@ -439,7 +446,10 @@ export default function UserProfile() {
{details.username || "-"}
-
+
-
Contact No
@@ -448,7 +458,10 @@ export default function UserProfile() {
-
+
-
Whatsapp No
@@ -456,7 +469,10 @@ export default function UserProfile() {
{details.alt_phone_number || "-"}
-
+
-
Email address
@@ -464,7 +480,10 @@ export default function UserProfile() {
{details.email || "-"}
-
+
-
First Name
@@ -472,7 +491,10 @@ export default function UserProfile() {
{details.first_name || "-"}
-
+
-
Last Name
@@ -480,7 +502,7 @@ export default function UserProfile() {
{details.last_name || "-"}
-
+
-
Age
@@ -497,7 +519,10 @@ export default function UserProfile() {
{details.user_type || "-"}
-
+
-
Gender
@@ -547,7 +572,10 @@ export default function UserProfile() {
-
+
-
Average weekly working hours
diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx
index 9a48a02ff87..7af73b017ba 100644
--- a/src/Redux/actions.tsx
+++ b/src/Redux/actions.tsx
@@ -803,30 +803,9 @@ export const listMedibaseMedicines = (
};
// Resource
-export const createResource = (params: object) => {
- return fireRequest("createResource", [], params);
-};
-export const updateResource = (id: string, params: object) => {
- return fireRequest("updateResource", [id], params);
-};
-export const deleteResourceRecord = (id: string) => {
- return fireRequest("deleteResourceRecord", [id], {});
-};
-export const listResourceRequests = (params: object, key: string) => {
- return fireRequest("listResourceRequests", [], params, null, key);
-};
-export const getResourceDetails = (pathParam: object) => {
- return fireRequest("getResourceDetails", [], {}, pathParam);
-};
export const downloadResourceRequests = (params: object) => {
return fireRequest("downloadResourceRequests", [], params);
};
-export const getResourceComments = (id: string, params: object) => {
- return fireRequest("getResourceComments", [], params, { id });
-};
-export const addResourceComments = (id: string, params: object) => {
- return fireRequest("addResourceComments", [], params, { id });
-};
export const listAssets = (params: object) =>
fireRequest("listAssets", [], params);
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index e34b090d6a5..046e720e119 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -41,10 +41,13 @@ import {
ILocalBodyByDistrict,
IPartialUpdateExternalResult,
} from "../Components/ExternalResult/models";
+
import { Prescription } from "../Components/Medicine/models";
-import { PatientModel } from "../Components/Patient/models";
+
import { UserModel } from "../Components/Users/models";
import { PaginatedResponse } from "../Utils/request/types";
+import { PatientModel } from "../Components/Patient/models";
+import { IComment, IResource } from "../Components/Resource/models";
/**
* A fake function that returns an empty object casted to type T
@@ -129,6 +132,8 @@ const routes = {
userList: {
path: "/api/v1/users/",
+ method: "GET",
+ TRes: Type
>(),
},
userListSkill: {
@@ -844,21 +849,31 @@ const routes = {
createResource: {
path: "/api/v1/resource/",
method: "POST",
+ TRes: Type(),
+ TBody: Type>(),
},
updateResource: {
- path: "/api/v1/resource",
+ path: "/api/v1/resource/{id}",
method: "PUT",
+ TRes: Type(),
+ TBody: Type>(),
},
deleteResourceRecord: {
- path: "/api/v1/resource",
+ path: "/api/v1/resource/{id}",
method: "DELETE",
+ TRes: Type<{
+ detail?: string;
+ }>(),
},
listResourceRequests: {
path: "/api/v1/resource/",
method: "GET",
+ TRes: Type>(),
},
getResourceDetails: {
path: "/api/v1/resource/{id}/",
+ method: "GET",
+ TRes: Type(),
},
downloadResourceRequests: {
path: "/api/v1/resource/",
@@ -867,10 +882,13 @@ const routes = {
getResourceComments: {
path: "/api/v1/resource/{id}/comment/",
method: "GET",
+ TRes: Type>(),
},
addResourceComments: {
path: "/api/v1/resource/{id}/comment/",
method: "POST",
+ TRes: Type(),
+ TBody: Type>(),
},
// Assets endpoints