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) => { />
-
diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index 35a125ae07d..9004dea939d 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -119,15 +119,17 @@ export const AutocompleteMutliSelect = ( onChange={(event) => setQuery(event.target.value.toLowerCase())} autoComplete="off" /> - -
- {props.isLoading ? ( - - ) : ( - - )} -
-
+ {!props.disabled && ( + +
+ {props.isLoading ? ( + + ) : ( + + )} +
+
+ )} {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) => ( -
-
-

{comment.comment}

-
-
- - {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) => ( +
+
+

{comment}

+
+
+ + {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