From 89465296081c508399b9372e8a9698bfd86ab0bb Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Wed, 20 Sep 2023 03:45:29 +0530 Subject: [PATCH 01/25] Asset page redirection (#6308) --- cypress/e2e/assets_spec/assets_manage.cy.ts | 24 +++++++++++++ .../pageobject/Facility/FacilityCreation.ts | 35 +++++++++++++++++++ src/Components/Facility/FacilityHome.tsx | 3 ++ 3 files changed, 62 insertions(+) diff --git a/cypress/e2e/assets_spec/assets_manage.cy.ts b/cypress/e2e/assets_spec/assets_manage.cy.ts index f27fd302a0c..1f89facd0a3 100644 --- a/cypress/e2e/assets_spec/assets_manage.cy.ts +++ b/cypress/e2e/assets_spec/assets_manage.cy.ts @@ -1,10 +1,17 @@ import { afterEach, before, beforeEach, cy, describe, it } from "local-cypress"; import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; +import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; +import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; describe("Asset", () => { const assetPage = new AssetPage(); const loginPage = new LoginPage(); + const facilityPage = new FacilityPage(); + const assetSearchPage = new AssetSearchPage(); + const assetFilters = new AssetFilters(); + const fillFacilityName = "Dummy Facility 1"; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -23,6 +30,23 @@ describe("Asset", () => { assetPage.verifyDeleteStatus(); }); + it("Verify Facility Asset Page Redirection", () => { + cy.visit("/facility"); + assetSearchPage.typeSearchKeyword(fillFacilityName); + assetSearchPage.pressEnter(); + facilityPage.verifyFacilityBadgeContent(fillFacilityName); + facilityPage.visitAlreadyCreatedFacility(); + facilityPage.clickManageFacilityDropdown(); + facilityPage.clickCreateAssetFacilityOption(); + facilityPage.verifyfacilitycreateassetredirection(); + facilityPage.verifyassetfacilitybackredirection(); + facilityPage.clickManageFacilityDropdown(); + facilityPage.clickviewAssetFacilityOption(); + facilityPage.verifyfacilityviewassetredirection(); + assetFilters.assertFacilityText(fillFacilityName); + facilityPage.verifyassetfacilitybackredirection(); + }); + afterEach(() => { cy.saveLocalStorage(); }); diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index ef4e65781e0..0c12d4655fd 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -120,6 +120,14 @@ class FacilityPage { cy.get("#configure-facility").contains("Configure Facility").click(); } + clickCreateAssetFacilityOption() { + cy.get("#create-assets").contains("Create Asset").click(); + } + + clickviewAssetFacilityOption() { + cy.get("#view-assets").contains("View Assets").click(); + } + clickInventoryManagementOption() { cy.get("#inventory-management", { timeout: 10000 }).should("be.visible"); cy.get("#inventory-management").click(); @@ -175,6 +183,33 @@ class FacilityPage { cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); } + verifyFacilityBadgeContent(expectedText: string) { + cy.get("[data-testid='Facility/District Name']").should( + "contain", + expectedText + ); + } + + verifyfacilitycreateassetredirection() { + cy.intercept("GET", "**/api/v1/facility/**").as("getNewAssets"); + cy.url().should("include", "/assets/new"); + cy.wait("@getNewAssets").its("response.statusCode").should("eq", 200); + } + + verifyassetfacilitybackredirection() { + cy.intercept("GET", "**/api/v1/facility/**").as("getManagePage"); + cy.go("back"); + cy.wait("@getManagePage").its("response.statusCode").should("eq", 200); + cy.get("#manage-facility-dropdown").scrollIntoView(); + cy.get("#manage-facility-dropdown").should("exist"); + } + + verifyfacilityviewassetredirection() { + cy.intercept("GET", "**api/v1/getallfacilities/**").as("getViewAssets"); + cy.url().should("include", "/assets?facility="); + cy.wait("@getViewAssets").its("response.statusCode").should("eq", 200); + } + clickManageInventory() { cy.contains("Manage Inventory").click(); } diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 81e7fa4906f..15317b1b56c 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -575,6 +575,7 @@ export const FacilityHome = (props: any) => { Resource Request navigate(`/facility/${facilityId}/assets/new`)} authorizeFor={NonReadOnlyUsers} icon={} @@ -582,12 +583,14 @@ export const FacilityHome = (props: any) => { Create Asset navigate(`/assets?facility=${facilityId}`)} icon={} > View Assets navigate(`/facility/${facilityId}/users`)} icon={} > From 90c0310e537922710cc321951d8297f9879b6f56 Mon Sep 17 00:00:00 2001 From: "Tasnimul H. Tauhid" Date: Wed, 20 Sep 2023 03:46:20 +0530 Subject: [PATCH 02/25] Renamed "Verified By" to "Treating Physician" (#6300) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/Components/Facility/ConsultationDetails/index.tsx | 2 +- src/Components/Facility/ConsultationForm.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index affdb2756cd..490c704f295 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -402,7 +402,7 @@ export const ConsultationDetails = (props: any) => { consultationData.deprecated_verified_by) && (
- Verified By:{" "} + Treating Physician:{" "} {consultationData.verified_by_object ? `${consultationData.verified_by_object.first_name} ${consultationData.verified_by_object.last_name}` diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 49028a62953..ca2301b5964 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -539,7 +539,7 @@ export const ConsultationForm = (props: any) => { case "verified_by": { if (state.form.suggestion !== "DD" && !state.form[field]) { - errors[field] = "Please fill verified by"; + errors[field] = "Please fill treating physician"; invalidForm = true; break; } @@ -1321,7 +1321,7 @@ export const ConsultationForm = (props: any) => { > Date: Wed, 20 Sep 2023 03:47:31 +0530 Subject: [PATCH 03/25] Changed requested values in all the places (#6292) --- src/Common/constants.tsx | 14 ++--- ...icalCare__NeurologicalMonitoringEditor.res | 2 +- .../CriticalCare__NeurologicalMonitoring.res | 56 +++++++++---------- .../Consultations/NeurologicalTables.tsx | 4 +- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index f5801e97e4b..c696beb1f15 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -663,22 +663,22 @@ export const NURSING_CARE_FIELDS: Array = [ export const EYE_OPEN_SCALE = [ { value: 4, text: "Spontaneous" }, { value: 3, text: "To Speech" }, - { value: 2, text: "Pain" }, - { value: 1, text: "None" }, + { value: 2, text: "To Pain" }, + { value: 1, text: "No Response" }, ]; export const VERBAL_RESPONSE_SCALE = [ - { value: 5, text: "Oriented/Coos/Babbles" }, + { value: 5, text: "Oriented to Time, Place and Person" }, { value: 4, text: "Confused/Irritable" }, { value: 3, text: "Inappropriate words/Cry to Pain" }, { value: 2, text: "Incomprehensible words/Moans to pain" }, - { value: 1, text: "None" }, + { value: 1, text: "No Response" }, ]; export const MOTOR_RESPONSE_SCALE = [ - { value: 6, text: "Obeying commands" }, - { value: 5, text: "Moves to localised pain" }, - { value: 4, text: "Flexion withdrawal from pain" }, + { value: 6, text: "Obeying commands/Normal acrivity" }, + { value: 5, text: "Moves to localized pain" }, + { value: 4, text: "Flexion/Withdrawal from pain" }, { value: 3, text: "Abnormal Flexion(decorticate)" }, { value: 2, text: "Abnormal Extension(decerebrate)" }, { value: 1, text: "No Response" }, diff --git a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res index f3f789e9dcd..35d137e0414 100644 --- a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res +++ b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res @@ -485,7 +485,7 @@ let make = (~updateCB, ~neurologicalMonitoring, ~id, ~consultationId) => {
{str("Glasgow Coma Scale")}
-
{str("Eye Open")}
+
{str("Eye Opening Response")}
{Js.Array.mapi( (x, i) => diff --git a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res index 1e8804c7524..f42ca5c140d 100644 --- a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res +++ b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res @@ -45,25 +45,25 @@ let make = ( ~limbResponseLowerExtremityRight, ~limbResponseLowerExtremityLeft, ) => { - inPronePosition: inPronePosition, - consciousnessLevel: consciousnessLevel, - consciousnessLevelDetails: consciousnessLevelDetails, - leftPupilSize: leftPupilSize, - leftPupilSizeDetails: leftPupilSizeDetails, - leftPupilLightReaction: leftPupilLightReaction, - leftPupilLightReactionDetails: leftPupilLightReactionDetails, - rightPupilSize: rightPupilSize, - rightPupilSizeDetails: rightPupilSizeDetails, - rightPupilLightReaction: rightPupilLightReaction, - rightPupilLightReactionDetails: rightPupilLightReactionDetails, - glasgowEyeOpen: glasgowEyeOpen, - glasgowVerbalResponse: glasgowVerbalResponse, - glasgowMotorResponse: glasgowMotorResponse, - glasgowTotalCalculated: glasgowTotalCalculated, - limbResponseUpperExtremityRight: limbResponseUpperExtremityRight, - limbResponseUpperExtremityLeft: limbResponseUpperExtremityLeft, - limbResponseLowerExtremityRight: limbResponseLowerExtremityRight, - limbResponseLowerExtremityLeft: limbResponseLowerExtremityLeft, + inPronePosition, + consciousnessLevel, + consciousnessLevelDetails, + leftPupilSize, + leftPupilSizeDetails, + leftPupilLightReaction, + leftPupilLightReactionDetails, + rightPupilSize, + rightPupilSizeDetails, + rightPupilLightReaction, + rightPupilLightReactionDetails, + glasgowEyeOpen, + glasgowVerbalResponse, + glasgowMotorResponse, + glasgowTotalCalculated, + limbResponseUpperExtremityRight, + limbResponseUpperExtremityLeft, + limbResponseLowerExtremityRight, + limbResponseLowerExtremityLeft, } let makeConsciousnessLevel = consciousnessLevel => { @@ -173,8 +173,8 @@ let limpResponseToString = limpResponse => { let eyeOpenToString = eyeOpen => { switch eyeOpen { - | 1 => "1 - None" - | 2 => "2 - Pain" + | 1 => "1 - No Response" + | 2 => "2 - To Pain" | 3 => "3 - To Speech" | 4 => "4 - Spontaneous" | _ => "Unknown" @@ -183,23 +183,23 @@ let eyeOpenToString = eyeOpen => { let motorResposneToString = eyeOpen => { switch eyeOpen { - | 1 => "1 - None" - | 2 => "2 - Incomprehensible words/Moans to pain" + | 1 => "1 - No Response" + | 2 => "2 - Abnormal Extension" | 3 => "3 - Abnormal Flexion" - | 4 => "4 - Withdrawing" - | 5 => "5 - Localizing/Withdrawl to touch" - | 6 => "6 - Obeying/Normal Activity" + | 4 => "4 - Flexion/Withdrawal to pain" + | 5 => "5 - Moves to localized pain" + | 6 => "6 - Obeys commands/Normal Activity" | _ => "Unknown" } } let verbalResposneToString = eyeOpen => { switch eyeOpen { - | 1 => "1 - None" + | 1 => "1 - No Response" | 2 => "2 - Incomprehensible words/Moans to pain" | 3 => "3 - Inappropriate words/Cry to pain" | 4 => "4 - Confused/Irritable" - | 5 => "5 - Oriented/Coos/Babbies" + | 5 => "5 - Oriented to Time, Place and Person" | _ => "Unknown" } } diff --git a/src/Components/Facility/Consultations/NeurologicalTables.tsx b/src/Components/Facility/Consultations/NeurologicalTables.tsx index 79a0675a708..89e9d598604 100644 --- a/src/Components/Facility/Consultations/NeurologicalTables.tsx +++ b/src/Components/Facility/Consultations/NeurologicalTables.tsx @@ -411,7 +411,9 @@ export const NeurologicalTable = (props: any) => {
Scale Description
-
Eye Open
+
+ Eye Opening Response +
{EYE_OPEN_SCALE.map((x: any) => (
Date: Wed, 20 Sep 2023 03:47:48 +0530 Subject: [PATCH 04/25] Medibase Search: Skip showing selected option search results when query is present (#6284) * fixes #6283; skip showing selected option in medibase search * fix onblur --- src/Components/Form/FormFields/Autocomplete.tsx | 1 + src/Components/Medicine/MedibaseAutocompleteFormField.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx index dcf3ab5e8b6..bbc60643faf 100644 --- a/src/Components/Form/FormFields/Autocomplete.tsx +++ b/src/Components/Form/FormFields/Autocomplete.tsx @@ -157,6 +157,7 @@ export const Autocomplete = (props: AutocompleteProps) => { placeholder={props.placeholder ?? "Select"} displayValue={(value: any) => value?.label || ""} onChange={(event) => setQuery(event.target.value.toLowerCase())} + onBlur={() => value && setQuery("")} autoComplete="off" /> diff --git a/src/Components/Medicine/MedibaseAutocompleteFormField.tsx b/src/Components/Medicine/MedibaseAutocompleteFormField.tsx index 337546691f9..2b5612f6f32 100644 --- a/src/Components/Medicine/MedibaseAutocompleteFormField.tsx +++ b/src/Components/Medicine/MedibaseAutocompleteFormField.tsx @@ -49,7 +49,7 @@ export default function MedibaseAutocompleteFormField( value={field.value} required onChange={field.handleChange} - options={options(field.value && [field.value])} + options={options(field.value && !query && [field.value])} optionLabel={(option) => option.name.toUpperCase()} optionDescription={(option) => } optionValue={(option) => option} From 4c8b840977c3766469cd9dc8332df1c288a37585 Mon Sep 17 00:00:00 2001 From: print-Sathvik <113630200+print-Sathvik@users.noreply.github.com> Date: Wed, 20 Sep 2023 03:50:37 +0530 Subject: [PATCH 05/25] Remove libphonenumber js (#6222) * Replaced libphonenumber-js with custom functions * used PhoneNumberValidator to validate * Fixed asset import cypress test --- package-lock.json | 6 - package.json | 1 - src/Common/constants.tsx | 132 ++++++++++++++++- src/Common/static/countryPhoneAndFlags.json | 54 +++---- src/Components/ExternalResult/ResultList.tsx | 4 +- src/Components/Facility/AssetCreate.tsx | 5 +- src/Components/Facility/FacilityCard.tsx | 9 +- src/Components/Facility/FacilityCreate.tsx | 20 +-- .../Form/FormFields/PhoneNumberFormField.tsx | 50 ++----- src/Components/Patient/ManagePatients.tsx | 8 +- src/Components/Patient/PatientRegister.tsx | 45 +++--- src/Components/Patient/ShiftCreate.tsx | 23 +-- src/Components/Resource/ResourceCreate.tsx | 16 +- src/Components/Shifting/ListFilter.tsx | 11 +- .../Shifting/ShiftDetailsUpdate.tsx | 9 +- src/Components/Users/UserAdd.tsx | 41 +++--- src/Components/Users/UserFilter.tsx | 10 +- src/Components/Users/UserProfile.tsx | 28 ++-- src/Utils/utils.ts | 139 +++++++++++++++++- 19 files changed, 425 insertions(+), 186 deletions(-) diff --git a/package-lock.json b/package-lock.json index db11b8c5220..6f655932fd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,6 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "libphonenumber-js": "^1.10.37", "lodash": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", @@ -12079,11 +12078,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libphonenumber-js": { - "version": "1.10.37", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.37.tgz", - "integrity": "sha512-Z10PCaOCiAxbUxLyR31DNeeNugSVP6iv/m7UrSKS5JHziEMApJtgku4e9Q69pzzSC9LnQiM09sqsGf2ticZnMw==" - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", diff --git a/package.json b/package.json index 2e894f22288..8ade8c604be 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "libphonenumber-js": "^1.10.37", "lodash": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index c696beb1f15..f64a9fa27ae 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1,9 +1,9 @@ import { IConfig } from "./hooks/useConfig"; import { PatientCategory } from "../Components/Facility/models"; import { SortOption } from "../Components/Common/SortDropdown"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; import { dateQueryString } from "../Utils/utils"; import { IconName } from "../CAREUI/icons/CareIcon"; +import { PhoneNumberValidator } from "../Components/Form/FieldValidators"; export const RESULTS_PER_PAGE_LIMIT = 14; export const PAGINATION_LIMIT = 36; @@ -938,13 +938,12 @@ export const XLSXAssetImportSchema = { prop: "support_phone", type: String, parse: (phone: number | string) => { - const parsed = parsePhoneNumberFromString(String(phone), "IN"); - - if (!parsed?.isValid()) { + phone = "+91" + String(phone); + if (!PhoneNumberValidator(["support"])(phone) === undefined) { throw new Error("Invalid Support Phone Number"); } - return parsed?.format("E.164"); + return phone ? phone : undefined; }, required: true, }, @@ -1001,3 +1000,126 @@ export const XLSXAssetImportSchema = { }, }, }; + +export const AREACODES: Record = { + CA: [ + "403", + "587", + "250", + "604", + "778", + "204", + "431", + "506", + "709", + "867", + "902", + "226", + "249", + "289", + "343", + "365", + "416", + "437", + "519", + "613", + "647", + "705", + "807", + "902", + "418", + "438", + "450", + "514", + "579", + "581", + "819", + "306", + "639", + "867", + ], + JM: ["658", "876"], + PR: ["787", "939"], + DO: ["809", "829"], + RE: ["262", "263", "692", "693"], + YT: ["269", "639"], + CC: ["89162"], + CX: ["89164"], + BQ: ["9"], + KZ: ["6", "7"], + SJ: ["79"], +}; + +export const IN_LANDLINE_AREA_CODES = [ + "11", + "22", + "33", + "44", + "20", + "40", + "79", + "80", + "120", + "124", + "129", + "135", + "141", + "160", + "161", + "172", + "175", + "181", + "183", + "233", + "240", + "241", + "250", + "251", + "253", + "257", + "260", + "261", + "265", + "343", + "413", + "422", + "431", + "435", + "452", + "462", + "471", + "474", + "477", + "478", + "481", + "484", + "485", + "487", + "490", + "497", + "512", + "522", + "532", + "542", + "551", + "562", + "581", + "591", + "621", + "612", + "641", + "657", + "712", + "721", + "724", + "751", + "761", + "821", + "824", + "831", + "836", + "866", + "870", + "891", + "4822", +]; diff --git a/src/Common/static/countryPhoneAndFlags.json b/src/Common/static/countryPhoneAndFlags.json index 9b2ea40427d..87d963444b5 100644 --- a/src/Common/static/countryPhoneAndFlags.json +++ b/src/Common/static/countryPhoneAndFlags.json @@ -2,20 +2,20 @@ "AD": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฉ", "name": "Andorra", "code": "376" }, "AE": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ช", "name": "United Arab Emirates", "code": "971" }, "AF": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ซ", "name": "Afghanistan", "code": "93" }, - "AG": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฌ", "name": "Antigua & Barbuda", "code": "+1-268" }, - "AI": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฎ", "name": "Anguilla", "code": "+1-264" }, + "AG": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฌ", "name": "Antigua & Barbuda", "code": "1-268" }, + "AI": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฎ", "name": "Anguilla", "code": "1-264" }, "AL": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฑ", "name": "Albania", "code": "355" }, "AM": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฒ", "name": "Armenia", "code": "374" }, "AO": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ด", "name": "Angola", "code": "244" }, "AR": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ท", "name": "Argentina", "code": "54" }, - "AS": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ธ", "name": "American Samoa", "code": "+1-684" }, + "AS": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ธ", "name": "American Samoa", "code": "1-684" }, "AT": { "flag": "๐Ÿ‡ฆ๐Ÿ‡น", "name": "Austria", "code": "43" }, "AU": { "flag": "๐Ÿ‡ฆ๐Ÿ‡บ", "name": "Australia", "code": "61" }, "AW": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ผ", "name": "Aruba", "code": "297" }, - "AX": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฝ", "name": "ร…land Islands", "code": "+358-18" }, + "AX": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฝ", "name": "ร…land Islands", "code": "358-18" }, "AZ": { "flag": "๐Ÿ‡ฆ๐Ÿ‡ฟ", "name": "Azerbaijan", "code": "994" }, "BA": { "flag": "๐Ÿ‡ง๐Ÿ‡ฆ", "name": "Bosnia & Herzegovina", "code": "387" }, - "BB": { "flag": "๐Ÿ‡ง๐Ÿ‡ง", "name": "Barbados", "code": "+1-246" }, + "BB": { "flag": "๐Ÿ‡ง๐Ÿ‡ง", "name": "Barbados", "code": "1-246" }, "BD": { "flag": "๐Ÿ‡ง๐Ÿ‡ฉ", "name": "Bangladesh", "code": "880" }, "BE": { "flag": "๐Ÿ‡ง๐Ÿ‡ช", "name": "Belgium", "code": "32" }, "BF": { "flag": "๐Ÿ‡ง๐Ÿ‡ซ", "name": "Burkina Faso", "code": "226" }, @@ -24,12 +24,12 @@ "BI": { "flag": "๐Ÿ‡ง๐Ÿ‡ฎ", "name": "Burundi", "code": "257" }, "BJ": { "flag": "๐Ÿ‡ง๐Ÿ‡ฏ", "name": "Benin", "code": "229" }, "BL": { "flag": "๐Ÿ‡ง๐Ÿ‡ฑ", "name": "St. Barthรฉlemy", "code": "590" }, - "BM": { "flag": "๐Ÿ‡ง๐Ÿ‡ฒ", "name": "Bermuda", "code": "+1-441" }, + "BM": { "flag": "๐Ÿ‡ง๐Ÿ‡ฒ", "name": "Bermuda", "code": "1-441" }, "BN": { "flag": "๐Ÿ‡ง๐Ÿ‡ณ", "name": "Brunei", "code": "673" }, "BO": { "flag": "๐Ÿ‡ง๐Ÿ‡ด", "name": "Bolivia", "code": "591" }, "BQ": { "flag": "๐Ÿ‡ง๐Ÿ‡ถ", "name": "Caribbean Netherlands", "code": "599" }, "BR": { "flag": "๐Ÿ‡ง๐Ÿ‡ท", "name": "Brazil", "code": "55" }, - "BS": { "flag": "๐Ÿ‡ง๐Ÿ‡ธ", "name": "Bahamas", "code": "+1-242" }, + "BS": { "flag": "๐Ÿ‡ง๐Ÿ‡ธ", "name": "Bahamas", "code": "1-242" }, "BT": { "flag": "๐Ÿ‡ง๐Ÿ‡น", "name": "Bhutan", "code": "975" }, "BW": { "flag": "๐Ÿ‡ง๐Ÿ‡ผ", "name": "Botswana", "code": "267" }, "BY": { "flag": "๐Ÿ‡ง๐Ÿ‡พ", "name": "Belarus", "code": "375" }, @@ -56,11 +56,11 @@ "DE": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ช", "name": "Germany", "code": "49" }, "DJ": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ฏ", "name": "Djibouti", "code": "253" }, "DK": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ฐ", "name": "Denmark", "code": "45" }, - "DM": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ฒ", "name": "Dominica", "code": "+1-767" }, + "DM": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ฒ", "name": "Dominica", "code": "1-767" }, "DO": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ด", "name": "Dominican Republic", - "code": "+1-809 and 1-829" + "code": "1" }, "DZ": { "flag": "๐Ÿ‡ฉ๐Ÿ‡ฟ", "name": "Algeria", "code": "213" }, "EC": { "flag": "๐Ÿ‡ช๐Ÿ‡จ", "name": "Ecuador", "code": "593" }, @@ -78,10 +78,10 @@ "FR": { "flag": "๐Ÿ‡ซ๐Ÿ‡ท", "name": "France", "code": "33" }, "GA": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฆ", "name": "Gabon", "code": "241" }, "GB": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ง", "name": "United Kingdom", "code": "44" }, - "GD": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฉ", "name": "Grenada", "code": "+1-473" }, + "GD": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฉ", "name": "Grenada", "code": "1-473" }, "GE": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ช", "name": "Georgia", "code": "995" }, "GF": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ซ", "name": "French Guiana", "code": "594" }, - "GG": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฌ", "name": "Guernsey", "code": "+44-1481" }, + "GG": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฌ", "name": "Guernsey", "code": "44-1481" }, "GH": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ญ", "name": "Ghana", "code": "233" }, "GI": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฎ", "name": "Gibraltar", "code": "350" }, "GL": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ฑ", "name": "Greenland", "code": "299" }, @@ -91,7 +91,7 @@ "GQ": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ถ", "name": "Equatorial Guinea", "code": "240" }, "GR": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ท", "name": "Greece", "code": "30" }, "GT": { "flag": "๐Ÿ‡ฌ๐Ÿ‡น", "name": "Guatemala", "code": "502" }, - "GU": { "flag": "๐Ÿ‡ฌ๐Ÿ‡บ", "name": "Guam", "code": "+1-671" }, + "GU": { "flag": "๐Ÿ‡ฌ๐Ÿ‡บ", "name": "Guam", "code": "1-671" }, "GW": { "flag": "๐Ÿ‡ฌ๐Ÿ‡ผ", "name": "Guinea-Bissau", "code": "245" }, "GY": { "flag": "๐Ÿ‡ฌ๐Ÿ‡พ", "name": "Guyana", "code": "592" }, "HK": { "flag": "๐Ÿ‡ญ๐Ÿ‡ฐ", "name": "Hong Kong SAR China", "code": "852" }, @@ -103,7 +103,7 @@ "ID": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ฉ", "name": "Indonesia", "code": "62" }, "IE": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ช", "name": "Ireland", "code": "353" }, "IL": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ฑ", "name": "Israel", "code": "972" }, - "IM": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ฒ", "name": "Isle of Man", "code": "+44-1624" }, + "IM": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ฒ", "name": "Isle of Man", "code": "44-1624" }, "IN": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ณ", "name": "India", "code": "91" }, "IO": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ด", @@ -114,8 +114,8 @@ "IR": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ท", "name": "Iran", "code": "98" }, "IS": { "flag": "๐Ÿ‡ฎ๐Ÿ‡ธ", "name": "Iceland", "code": "354" }, "IT": { "flag": "๐Ÿ‡ฎ๐Ÿ‡น", "name": "Italy", "code": "39" }, - "JE": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ช", "name": "Jersey", "code": "+44-1534" }, - "JM": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ฒ", "name": "Jamaica", "code": "+1-876" }, + "JE": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ช", "name": "Jersey", "code": "44-1534" }, + "JM": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ฒ", "name": "Jamaica", "code": "1" }, "JO": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ด", "name": "Jordan", "code": "962" }, "JP": { "flag": "๐Ÿ‡ฏ๐Ÿ‡ต", "name": "Japan", "code": "81" }, "KE": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ช", "name": "Kenya", "code": "254" }, @@ -123,15 +123,15 @@ "KH": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ญ", "name": "Cambodia", "code": "855" }, "KI": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ฎ", "name": "Kiribati", "code": "686" }, "KM": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ฒ", "name": "Comoros", "code": "269" }, - "KN": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ณ", "name": "St. Kitts & Nevis", "code": "+1-869" }, + "KN": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ณ", "name": "St. Kitts & Nevis", "code": "1-869" }, "KP": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ต", "name": "North Korea", "code": "850" }, "KR": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ท", "name": "South Korea", "code": "82" }, "KW": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ผ", "name": "Kuwait", "code": "965" }, - "KY": { "flag": "๐Ÿ‡ฐ๐Ÿ‡พ", "name": "Cayman Islands", "code": "+1-345" }, + "KY": { "flag": "๐Ÿ‡ฐ๐Ÿ‡พ", "name": "Cayman Islands", "code": "1-345" }, "KZ": { "flag": "๐Ÿ‡ฐ๐Ÿ‡ฟ", "name": "Kazakhstan", "code": "7" }, "LA": { "flag": "๐Ÿ‡ฑ๐Ÿ‡ฆ", "name": "Laos", "code": "856" }, "LB": { "flag": "๐Ÿ‡ฑ๐Ÿ‡ง", "name": "Lebanon", "code": "961" }, - "LC": { "flag": "๐Ÿ‡ฑ๐Ÿ‡จ", "name": "St. Lucia", "code": "+1-758" }, + "LC": { "flag": "๐Ÿ‡ฑ๐Ÿ‡จ", "name": "St. Lucia", "code": "1-758" }, "LI": { "flag": "๐Ÿ‡ฑ๐Ÿ‡ฎ", "name": "Liechtenstein", "code": "423" }, "LK": { "flag": "๐Ÿ‡ฑ๐Ÿ‡ฐ", "name": "Sri Lanka", "code": "94" }, "LR": { "flag": "๐Ÿ‡ฑ๐Ÿ‡ท", "name": "Liberia", "code": "231" }, @@ -152,10 +152,10 @@ "MM": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ฒ", "name": "Myanmar (Burma)", "code": "95" }, "MN": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ณ", "name": "Mongolia", "code": "976" }, "MO": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ด", "name": "Macao SAR China", "code": "853" }, - "MP": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ต", "name": "Northern Mariana Islands", "code": "+1-670" }, + "MP": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ต", "name": "Northern Mariana Islands", "code": "1-670" }, "MQ": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ถ", "name": "Martinique", "code": "596" }, "MR": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ท", "name": "Mauritania", "code": "222" }, - "MS": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ธ", "name": "Montserrat", "code": "+1-664" }, + "MS": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ธ", "name": "Montserrat", "code": "1-664" }, "MT": { "flag": "๐Ÿ‡ฒ๐Ÿ‡น", "name": "Malta", "code": "356" }, "MU": { "flag": "๐Ÿ‡ฒ๐Ÿ‡บ", "name": "Mauritius", "code": "230" }, "MV": { "flag": "๐Ÿ‡ฒ๐Ÿ‡ป", "name": "Maldives", "code": "960" }, @@ -185,7 +185,7 @@ "PL": { "flag": "๐Ÿ‡ต๐Ÿ‡ฑ", "name": "Poland", "code": "48" }, "PM": { "flag": "๐Ÿ‡ต๐Ÿ‡ฒ", "name": "St. Pierre & Miquelon", "code": "508" }, "PN": { "flag": "๐Ÿ‡ต๐Ÿ‡ณ", "name": "Pitcairn Islands", "code": "870" }, - "PR": { "flag": "๐Ÿ‡ต๐Ÿ‡ท", "name": "Puerto Rico", "code": "+1-787 and 1-939" }, + "PR": { "flag": "๐Ÿ‡ต๐Ÿ‡ท", "name": "Puerto Rico", "code": "1" }, "PS": { "flag": "๐Ÿ‡ต๐Ÿ‡ธ", "name": "Palestinian Territories", "code": "970" }, "PT": { "flag": "๐Ÿ‡ต๐Ÿ‡น", "name": "Portugal", "code": "351" }, "PW": { "flag": "๐Ÿ‡ต๐Ÿ‡ผ", "name": "Palau", "code": "680" }, @@ -214,10 +214,10 @@ "SS": { "flag": "๐Ÿ‡ธ๐Ÿ‡ธ", "name": "South Sudan", "code": "211" }, "ST": { "flag": "๐Ÿ‡ธ๐Ÿ‡น", "name": "Sรฃo Tomรฉ & Prรญncipe", "code": "239" }, "SV": { "flag": "๐Ÿ‡ธ๐Ÿ‡ป", "name": "El Salvador", "code": "503" }, - "SX": { "flag": "๐Ÿ‡ธ๐Ÿ‡ฝ", "name": "Sint Maarten", "code": "599" }, + "SX": { "flag": "๐Ÿ‡ธ๐Ÿ‡ฝ", "name": "Sint Maarten", "code": "1-721" }, "SY": { "flag": "๐Ÿ‡ธ๐Ÿ‡พ", "name": "Syria", "code": "963" }, "SZ": { "flag": "๐Ÿ‡ธ๐Ÿ‡ฟ", "name": "Eswatini", "code": "268" }, - "TC": { "flag": "๐Ÿ‡น๐Ÿ‡จ", "name": "Turks & Caicos Islands", "code": "+1-649" }, + "TC": { "flag": "๐Ÿ‡น๐Ÿ‡จ", "name": "Turks & Caicos Islands", "code": "1-649" }, "TD": { "flag": "๐Ÿ‡น๐Ÿ‡ฉ", "name": "Chad", "code": "235" }, "TG": { "flag": "๐Ÿ‡น๐Ÿ‡ฌ", "name": "Togo", "code": "228" }, "TH": { "flag": "๐Ÿ‡น๐Ÿ‡ญ", "name": "Thailand", "code": "66" }, @@ -228,7 +228,7 @@ "TN": { "flag": "๐Ÿ‡น๐Ÿ‡ณ", "name": "Tunisia", "code": "216" }, "TO": { "flag": "๐Ÿ‡น๐Ÿ‡ด", "name": "Tonga", "code": "676" }, "TR": { "flag": "๐Ÿ‡น๐Ÿ‡ท", "name": "Turkey", "code": "90" }, - "TT": { "flag": "๐Ÿ‡น๐Ÿ‡น", "name": "Trinidad & Tobago", "code": "+1-868" }, + "TT": { "flag": "๐Ÿ‡น๐Ÿ‡น", "name": "Trinidad & Tobago", "code": "1-868" }, "TV": { "flag": "๐Ÿ‡น๐Ÿ‡ป", "name": "Tuvalu", "code": "688" }, "TW": { "flag": "๐Ÿ‡น๐Ÿ‡ผ", "name": "Taiwan", "code": "886" }, "TZ": { "flag": "๐Ÿ‡น๐Ÿ‡ฟ", "name": "Tanzania", "code": "255" }, @@ -239,10 +239,10 @@ "UY": { "flag": "๐Ÿ‡บ๐Ÿ‡พ", "name": "Uruguay", "code": "598" }, "UZ": { "flag": "๐Ÿ‡บ๐Ÿ‡ฟ", "name": "Uzbekistan", "code": "998" }, "VA": { "flag": "๐Ÿ‡ป๐Ÿ‡ฆ", "name": "Vatican City", "code": "379" }, - "VC": { "flag": "๐Ÿ‡ป๐Ÿ‡จ", "name": "St. Vincent & Grenadines", "code": "+1-784" }, + "VC": { "flag": "๐Ÿ‡ป๐Ÿ‡จ", "name": "St. Vincent & Grenadines", "code": "1-784" }, "VE": { "flag": "๐Ÿ‡ป๐Ÿ‡ช", "name": "Venezuela", "code": "58" }, - "VG": { "flag": "๐Ÿ‡ป๐Ÿ‡ฌ", "name": "British Virgin Islands", "code": "+1-284" }, - "VI": { "flag": "๐Ÿ‡ป๐Ÿ‡ฎ", "name": "U.S. Virgin Islands", "code": "+1-340" }, + "VG": { "flag": "๐Ÿ‡ป๐Ÿ‡ฌ", "name": "British Virgin Islands", "code": "1-284" }, + "VI": { "flag": "๐Ÿ‡ป๐Ÿ‡ฎ", "name": "U.S. Virgin Islands", "code": "1-340" }, "VN": { "flag": "๐Ÿ‡ป๐Ÿ‡ณ", "name": "Vietnam", "code": "84" }, "VU": { "flag": "๐Ÿ‡ป๐Ÿ‡บ", "name": "Vanuatu", "code": "678" }, "WF": { "flag": "๐Ÿ‡ผ๐Ÿ‡ซ", "name": "Wallis & Futuna", "code": "681" }, diff --git a/src/Components/ExternalResult/ResultList.tsx b/src/Components/ExternalResult/ResultList.tsx index 18de6f29134..74fbf8430b0 100644 --- a/src/Components/ExternalResult/ResultList.tsx +++ b/src/Components/ExternalResult/ResultList.tsx @@ -6,7 +6,7 @@ import { externalResultList } from "../../Redux/actions"; import ListFilter from "./ListFilter"; import FacilitiesSelectDialogue from "./FacilitiesSelectDialogue"; import { FacilityModel } from "../Facility/models"; -import parsePhoneNumberFromString from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils"; import SearchInput from "../Form/SearchInput"; import useFilters from "../../Common/hooks/useFilters"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -65,7 +65,7 @@ export default function ResultList() { page: qParams.page || 1, name: qParams.name || "", mobile_number: qParams.mobile_number - ? parsePhoneNumberFromString(qParams.mobile_number)?.format("E.164") + ? parsePhoneNumber(qParams.mobile_number) ?? "" : "", wards: qParams.wards || undefined, local_bodies: qParams.local_bodies || undefined, diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index 1e6eead5242..156d738857a 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -30,13 +30,12 @@ import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import { navigate } from "raviger"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; import { parseQueryParams } from "../../Utils/primitives"; import useAppHistory from "../../Common/hooks/useAppHistory"; import { useDispatch } from "react-redux"; import useVisibility from "../../Utils/useVisibility"; import { validateEmailAddress } from "../../Common/validation"; -import { dateQueryString } from "../../Utils/utils.js"; +import { dateQueryString, parsePhoneNumber } from "../../Utils/utils.js"; import dayjs from "../../Utils/dayjs"; import DateInputV2 from "../Common/DateInputV2.js"; @@ -341,7 +340,7 @@ const AssetCreate = (props: AssetProps) => { support_email: support_email, support_phone: support_phone.startsWith("1800") ? support_phone - : parsePhoneNumberFromString(support_phone)?.format("E.164"), + : parsePhoneNumber(support_phone), qr_code_id: qrCodeId !== "" ? qrCodeId : null, manufacturer: manufacturer, warranty_amc_end_of_validity: warranty_amc_end_of_validity diff --git a/src/Components/Facility/FacilityCard.tsx b/src/Components/Facility/FacilityCard.tsx index e66fc4f3cc2..43e515cec93 100644 --- a/src/Components/Facility/FacilityCard.tsx +++ b/src/Components/Facility/FacilityCard.tsx @@ -9,7 +9,7 @@ import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; import * as Notification from "../../Utils/Notifications.js"; import Chip from "../../CAREUI/display/Chip"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import { parsePhoneNumber } from "libphonenumber-js"; +import { formatPhoneNumber, parsePhoneNumber } from "../../Utils/utils"; import DialogModal from "../Common/Dialog"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import useConfig from "../../Common/hooks/useConfig"; @@ -147,10 +147,9 @@ export const FacilityCard = (props: { facility: any; userType: any }) => { href={`tel:${facility.phone_number}`} className="text-sm font-semibold tracking-wider" > - {parsePhoneNumber( - facility.phone_number as string, - "IN" - ).formatInternational() || "-"} + {formatPhoneNumber( + parsePhoneNumber(facility.phone_number as string) ?? "-" + )}
diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index d1243daaf1d..e742caefb82 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -26,7 +26,11 @@ import { listDoctor, updateFacility, } from "../../Redux/actions"; -import { getPincodeDetails, includesIgnoreCase } from "../../Utils/utils"; +import { + getPincodeDetails, + includesIgnoreCase, + parsePhoneNumber, +} from "../../Utils/utils"; import { phonePreg, validateLatitude, @@ -51,11 +55,11 @@ import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import { navigate } from "raviger"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; +import { PhoneNumberValidator } from "../Form/FieldValidators.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -434,11 +438,11 @@ export const FacilityCreate = (props: FacilityProps) => { return; case "phone_number": // eslint-disable-next-line no-case-declarations - const phoneNumber = parsePhoneNumberFromString(state.form[field]); + const phoneNumber = state.form[field]; if ( - !state.form[field] || - !phoneNumber?.isPossible() || - !phonePreg(String(phoneNumber?.number)) + !phoneNumber || + !PhoneNumberValidator()(phoneNumber) === undefined || + !phonePreg(phoneNumber) ) { errors[field] = t("invalid_phone_number"); invalidForm = true; @@ -488,9 +492,7 @@ export const FacilityCreate = (props: FacilityProps) => { kasp_empanelled: JSON.parse(state.form.kasp_empanelled), latitude: state.form.latitude || null, longitude: state.form.longitude || null, - phone_number: parsePhoneNumberFromString( - state.form.phone_number - )?.format("E.164"), + phone_number: parsePhoneNumber(state.form.phone_number), oxygen_capacity: state.form.oxygen_capacity ? state.form.oxygen_capacity : 0, diff --git a/src/Components/Form/FormFields/PhoneNumberFormField.tsx b/src/Components/Form/FormFields/PhoneNumberFormField.tsx index 64c93c51500..6aa70f801de 100644 --- a/src/Components/Form/FormFields/PhoneNumberFormField.tsx +++ b/src/Components/Form/FormFields/PhoneNumberFormField.tsx @@ -1,12 +1,13 @@ import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils"; import FormField from "./FormField"; +import { useEffect, useMemo, useState } from "react"; import { - AsYouType, - isValidPhoneNumber, + classNames, parsePhoneNumber, -} from "libphonenumber-js"; -import { useMemo, useState } from "react"; -import { classNames } from "../../../Utils/utils"; + formatPhoneNumber as formatPhoneNumberUtil, + getCountryCode, + CountryData, +} from "../../../Utils/utils"; import phoneCodesJson from "../../../Common/static/countryPhoneAndFlags.json"; import { FieldError, @@ -15,12 +16,6 @@ import { } from "../FieldValidators"; import CareIcon from "../../../CAREUI/icons/CareIcon"; -interface CountryData { - flag: string; - name: string; - code: string; -} - const phoneCodes: Record = phoneCodesJson; interface Props extends FormFieldBaseProps { @@ -39,21 +34,6 @@ export default function PhoneNumberFormField(props: Props) { [props.types] ); - const asYouType = useMemo(() => { - const asYouType = new AsYouType(); - - asYouType.reset(); - - if (field.value) { - asYouType.input(field.value); - } else { - asYouType.input("+91"); - field.handleChange(asYouType.getNumberValue()); - } - - return asYouType; - }, []); - const validate = useMemo( () => (value: string | undefined, event: "blur" | "change") => { if (!value || props.disableValidation) { @@ -73,9 +53,9 @@ export default function PhoneNumberFormField(props: Props) { const setValue = (value: string) => { value = value.replaceAll(/[^0-9+]/g, ""); - - asYouType.reset(); - asYouType.input(value); + if (value.length > 12 && value.startsWith("+910")) { + value = "+91" + value.slice(4); + } const error = validate(value, "change"); field.handleChange(value); @@ -83,6 +63,8 @@ export default function PhoneNumberFormField(props: Props) { setError(error); }; + useEffect(() => setValue(field.value || "+91"), []); + return ( setValue(e.target.value)} disabled={field.disabled} onBlur={() => setError(validate(field.value, "blur"))} @@ -122,7 +104,7 @@ export default function PhoneNumberFormField(props: Props) { autoComplete="country" className="cui-input-base h-full border-0 bg-transparent pl-2 pr-8 text-end font-medium tracking-wider text-gray-700 focus:ring-2 focus:ring-inset" value={ - asYouType.getCountry() ?? + getCountryCode(field.value) ?? (field.value?.startsWith("1800") ? "1800" : "Other") } onChange={(e) => { @@ -176,15 +158,15 @@ const conditionPhoneCode = (code: string) => { return code.startsWith("+") ? code : "+" + code; }; -const formatPhoneNumber = (value: string) => { +const formatPhoneNumber = (value: string, types: PhoneNumberType[]) => { if (value === undefined || value === null) { return "+91 "; } - if (!isValidPhoneNumber(value)) { + if (PhoneNumberValidator(types)(value) !== undefined || value.length < 13) { return value; } const phoneNumber = parsePhoneNumber(value); - return phoneNumber.formatInternational(); + return phoneNumber ? formatPhoneNumberUtil(phoneNumber) : value; }; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 54d22b5f54c..71a3ad08c8c 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -38,7 +38,7 @@ import SortDropdownMenu from "../Common/SortDropdown"; import SwitchTabs from "../Common/components/SwitchTabs"; import SwipeableViews from "react-swipeable-views"; import { parseOptionId } from "../../Common/utils"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils.js"; import { useDispatch } from "react-redux"; import useFilters from "../../Common/hooks/useFilters"; import { useTranslation } from "react-i18next"; @@ -161,12 +161,10 @@ export const PatientManager = () => { (qParams.is_active || "True"), disease_status: qParams.disease_status || undefined, phone_number: qParams.phone_number - ? parsePhoneNumberFromString(qParams.phone_number)?.format("E.164") + ? parsePhoneNumber(qParams.phone_number) : undefined, emergency_phone_number: qParams.emergency_phone_number - ? parsePhoneNumberFromString(qParams.emergency_phone_number)?.format( - "E.164" - ) + ? parsePhoneNumber(qParams.emergency_phone_number) : undefined, local_body: qParams.lsgBody || undefined, facility: qParams.facility, diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index d52ce840090..41f36bffd35 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -25,6 +25,7 @@ import { dateQueryString, getPincodeDetails, includesIgnoreCase, + parsePhoneNumber, } from "../../Utils/utils"; import { navigate, useQueryParams } from "raviger"; import { statusType, useAbortableEffect } from "../../Common/utils"; @@ -40,7 +41,11 @@ import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; import { DupPatientModel } from "../Facility/models"; import DuplicatePatientDialog from "../Facility/DuplicatePatientDialog"; -import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import { + FieldError, + PhoneNumberValidator, + RequiredFieldValidator, +} from "../Form/FieldValidators"; import { FieldErrorText, FieldLabel } from "../Form/FormFields/FormField"; import Form from "../Form/Form"; import { HCXPolicyModel } from "../HCX/models"; @@ -58,7 +63,6 @@ import TransferPatientDialog from "../Facility/TransferPatientDialog"; import countryList from "../../Common/static/countries.json"; import { debounce } from "lodash"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; import { useDispatch } from "react-redux"; @@ -98,7 +102,7 @@ const initForm: any = { age: "", gender: "", phone_number: "+91", - emergency_phone_number: null, + emergency_phone_number: "+91", blood_group: "", disease_status: diseaseStatus[2], is_declared_positive: "false", @@ -596,14 +600,22 @@ export const PatientRegister = (props: PatientRegisterProps) => { } return; case "phone_number": - phoneNumber = parsePhoneNumberFromString(form[field]); - if (!form[field] || !phoneNumber?.isPossible()) { + phoneNumber = parsePhoneNumber(form[field]); + if ( + !form[field] || + !phoneNumber || + !PhoneNumberValidator()(phoneNumber) === undefined + ) { errors[field] = "Please enter valid phone number"; } return; case "emergency_phone_number": - emergency_phone_number = parsePhoneNumberFromString(form[field]); - if (!form[field] || !emergency_phone_number?.isPossible()) { + emergency_phone_number = parsePhoneNumber(form[field]); + if ( + !form[field] || + !emergency_phone_number || + !PhoneNumberValidator()(emergency_phone_number) === undefined + ) { errors[field] = "Please enter valid phone number"; } return; @@ -729,12 +741,8 @@ export const PatientRegister = (props: PatientRegisterProps) => { }); const data = { abha_number: state.form.abha_number, - phone_number: parsePhoneNumberFromString(formData.phone_number)?.format( - "E.164" - ), - emergency_phone_number: parsePhoneNumberFromString( - formData.emergency_phone_number - )?.format("E.164"), + phone_number: parsePhoneNumber(formData.phone_number), + emergency_phone_number: parsePhoneNumber(formData.emergency_phone_number), date_of_birth: dateQueryString(formData.date_of_birth), disease_status: formData.disease_status, date_of_test: formData.date_of_test ? formData.date_of_test : undefined, @@ -912,12 +920,12 @@ export const PatientRegister = (props: PatientRegisterProps) => { if (mobile) { field("phone_number").onChange({ name: "phone_number", - value: parsePhoneNumberFromString(mobile, "IN")?.format("E.164"), + value: parsePhoneNumber(mobile), }); field("emergency_phone_number").onChange({ name: "emergency_phone_number", - value: parsePhoneNumberFromString(mobile, "IN")?.format("E.164"), + value: parsePhoneNumber(mobile), }); } @@ -958,9 +966,12 @@ export const PatientRegister = (props: PatientRegisterProps) => { const duplicateCheck = useCallback( debounce(async (phoneNo: string) => { - if (phoneNo && parsePhoneNumberFromString(phoneNo)?.isPossible()) { + if ( + phoneNo && + PhoneNumberValidator()(parsePhoneNumber(phoneNo) ?? "") === undefined + ) { const query = { - phone_number: parsePhoneNumberFromString(phoneNo)?.format("E.164"), + phone_number: parsePhoneNumber(phoneNo), }; const res = await dispatchAction(searchPatient(query)); if (res?.data?.results) { diff --git a/src/Components/Patient/ShiftCreate.tsx b/src/Components/Patient/ShiftCreate.tsx index 7283fb3d5d7..e8e89ebc59c 100644 --- a/src/Components/Patient/ShiftCreate.tsx +++ b/src/Components/Patient/ShiftCreate.tsx @@ -18,7 +18,7 @@ import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import { navigate } from "raviger"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils.js"; import { phonePreg } from "../../Common/validation"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; @@ -28,6 +28,7 @@ import Page from "../Common/components/Page.js"; import Card from "../../CAREUI/display/Card.js"; import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField.js"; import { SelectFormField } from "../Form/FormFields/SelectFormField.js"; +import { PhoneNumberValidator } from "../Form/FieldValidators.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -56,13 +57,13 @@ export const ShiftCreate = (props: patientShiftProps) => { vehicle_preference: "", comments: "", refering_facility_contact_name: "", - refering_facility_contact_number: "+91", + refering_facility_contact_number: "", assigned_facility_type: null, preferred_vehicle_choice: null, breathlessness_level: null, patient_category: "", ambulance_driver_name: "", - ambulance_phone_number: undefined, + ambulance_phone_number: "", ambulance_number: "", }; @@ -162,10 +163,10 @@ export const ShiftCreate = (props: patientShiftProps) => { errors[field] = requiredFields[field].errorText; isInvalidForm = true; } else if ( - !parsePhoneNumberFromString(state.form[field])?.isPossible() || - !phonePreg( - String(parsePhoneNumberFromString(state.form[field])?.number) - ) + !PhoneNumberValidator()( + parsePhoneNumber(state.form[field]) ?? "" + ) === undefined || + !phonePreg(String(parsePhoneNumber(state.form[field]))) ) { errors[field] = requiredFields[field].invalidText; isInvalidForm = true; @@ -222,15 +223,15 @@ export const ShiftCreate = (props: patientShiftProps) => { preferred_vehicle_choice: state.form.preferred_vehicle_choice, refering_facility_contact_name: state.form.refering_facility_contact_name, - refering_facility_contact_number: parsePhoneNumberFromString( + refering_facility_contact_number: parsePhoneNumber( state.form.refering_facility_contact_number - )?.format("E.164"), + ), breathlessness_level: state.form.breathlessness_level, patient_category: patientCategory, ambulance_driver_name: state.form.ambulance_driver_name, - ambulance_phone_number: parsePhoneNumberFromString( + ambulance_phone_number: parsePhoneNumber( state.form.ambulance_phone_number - )?.format("E.164"), + ), ambulance_number: state.form.ambulance_number, }; diff --git a/src/Components/Resource/ResourceCreate.tsx b/src/Components/Resource/ResourceCreate.tsx index 417455d1ec1..cf9e5f6f22a 100644 --- a/src/Components/Resource/ResourceCreate.tsx +++ b/src/Components/Resource/ResourceCreate.tsx @@ -9,7 +9,7 @@ import { RESOURCE_CATEGORY_CHOICES, RESOURCE_SUBCATEGORIES, } from "../../Common/constants"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils"; import { phonePreg } from "../../Common/validation"; import { createResource, getAnyFacility } from "../../Redux/actions"; @@ -25,6 +25,7 @@ import RadioFormField from "../Form/FormFields/RadioFormField"; import { FieldLabel } from "../Form/FormFields/FormField"; import Card from "../../CAREUI/display/Card"; import Page from "../Common/components/Page"; +import { PhoneNumberValidator } from "../Form/FieldValidators"; const Loading = lazy(() => import("../Common/Loading")); @@ -41,7 +42,7 @@ const initForm: any = { title: "", reason: "", refering_facility_contact_name: "", - refering_facility_contact_number: "", + refering_facility_contact_number: "+91", required_quantity: null, }; @@ -131,13 +132,14 @@ export default function ResourceCreate(props: resourceProps) { Object.keys(requiredFields).forEach((field) => { switch (field) { case "refering_facility_contact_number": { - const phoneNumber = parsePhoneNumberFromString(state.form[field]); + const phoneNumber = parsePhoneNumber(state.form[field]); if (!state.form[field]) { errors[field] = requiredFields[field].errorText; isInvalidForm = true; } else if ( - !phoneNumber?.isPossible() || - !phonePreg(String(phoneNumber?.number)) + !phoneNumber || + !PhoneNumberValidator()(phoneNumber) === undefined || + !phonePreg(String(phoneNumber)) ) { errors[field] = requiredFields[field].invalidText; isInvalidForm = true; @@ -194,9 +196,9 @@ export default function ResourceCreate(props: resourceProps) { reason: state.form.reason, refering_facility_contact_name: state.form.refering_facility_contact_name, - refering_facility_contact_number: parsePhoneNumberFromString( + refering_facility_contact_number: parsePhoneNumber( state.form.refering_facility_contact_number - )?.format("E.164"), + ), requested_quantity: state.form.requested_quantity || 0, }; diff --git a/src/Components/Shifting/ListFilter.tsx b/src/Components/Shifting/ListFilter.tsx index afdc087973f..5f2233c2263 100644 --- a/src/Components/Shifting/ListFilter.tsx +++ b/src/Components/Shifting/ListFilter.tsx @@ -19,13 +19,12 @@ import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import { navigate } from "raviger"; -import parsePhoneNumberFromString from "libphonenumber-js"; import useConfig from "../../Common/hooks/useConfig"; import { useDispatch } from "react-redux"; import useMergeState from "../../Common/hooks/useMergeState"; import { useTranslation } from "react-i18next"; import UserAutocompleteFormField from "../Common/UserAutocompleteFormField"; -import { dateQueryString } from "../../Utils/utils"; +import { dateQueryString, parsePhoneNumber } from "../../Utils/utils"; import dayjs from "dayjs"; const clearFilterState = { @@ -207,10 +206,10 @@ export default function ListFilter(props: any) { assigned_facility: assigned_facility || "", emergency: emergency || "", is_up_shift: is_up_shift || "", - patient_phone_number: patient_phone_number - ? parsePhoneNumberFromString(patient_phone_number)?.format("E.164") ?? - "" - : "", + patient_phone_number: + patient_phone_number === "+91" + ? "" + : parsePhoneNumber(patient_phone_number) ?? "", created_date_before: dateQueryString(created_date_before), created_date_after: dateQueryString(created_date_after), modified_date_before: dateQueryString(modified_date_before), diff --git a/src/Components/Shifting/ShiftDetailsUpdate.tsx b/src/Components/Shifting/ShiftDetailsUpdate.tsx index dd10bae6a96..ec4eab661d9 100644 --- a/src/Components/Shifting/ShiftDetailsUpdate.tsx +++ b/src/Components/Shifting/ShiftDetailsUpdate.tsx @@ -23,7 +23,7 @@ import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField.js"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils.js"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; import { useDispatch } from "react-redux"; @@ -222,9 +222,10 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => { breathlessness_level: state.form.breathlessness_level, patient_category: state.form.patient_category, ambulance_driver_name: state.form.ambulance_driver_name, - ambulance_phone_number: parsePhoneNumberFromString( - state.form.ambulance_phone_number - )?.format("E.164"), + ambulance_phone_number: + state.form.ambulance_phone_number === "+91" + ? "" + : parsePhoneNumber(state.form.ambulance_phone_number), ambulance_number: state.form.ambulance_number, }; diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx index 18b1e9fa73d..a6553bad01b 100644 --- a/src/Components/Users/UserAdd.tsx +++ b/src/Components/Users/UserAdd.tsx @@ -1,5 +1,4 @@ import { Link, navigate } from "raviger"; -import { parsePhoneNumberFromString } from "libphonenumber-js/max"; import { lazy, useCallback, useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import { @@ -25,8 +24,11 @@ import { import * as Notification from "../../Utils/Notifications.js"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FacilityModel } from "../Facility/models"; - -import { classNames, dateQueryString } from "../../Utils/utils"; +import { + classNames, + dateQueryString, + parsePhoneNumber, +} from "../../Utils/utils"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import TextFormField from "../Form/FormFields/TextFormField"; @@ -42,6 +44,7 @@ import CircularProgress from "../Common/components/CircularProgress"; import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; import dayjs from "../../Utils/dayjs"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import { PhoneNumberValidator } from "../Form/FieldValidators"; const Loading = lazy(() => import("../Common/Loading")); @@ -472,14 +475,11 @@ export const UserAdd = (props: UserProps) => { return; case "phone_number": // eslint-disable-next-line no-case-declarations - const phoneNumber = parsePhoneNumberFromString( - state.form[field], - "IN" - ); + const phoneNumber = parsePhoneNumber(state.form[field]); // eslint-disable-next-line no-case-declarations let is_valid = false; if (phoneNumber) { - is_valid = phoneNumber.isValid(); + is_valid = PhoneNumberValidator()(phoneNumber) === undefined; } if (!state.form[field] || !is_valid) { errors[field] = "Please enter valid phone number"; @@ -491,12 +491,10 @@ export const UserAdd = (props: UserProps) => { // eslint-disable-next-line no-case-declarations let alt_is_valid = false; if (state.form[field] && state.form[field] !== "+91") { - const altPhoneNumber = parsePhoneNumberFromString( - state.form[field], - "IN" - ); + const altPhoneNumber = parsePhoneNumber(state.form[field]); if (altPhoneNumber) { - alt_is_valid = altPhoneNumber.isValid(); + alt_is_valid = + PhoneNumberValidator(["mobile"])(altPhoneNumber) === undefined; } } if ( @@ -572,15 +570,20 @@ export const UserAdd = (props: UserProps) => { state: state.form.state, district: state.form.district, local_body: showLocalbody ? state.form.local_body : null, - phone_number: parsePhoneNumberFromString( - state.form.phone_number - )?.format("E.164"), + phone_number: + state.form.phone_number === "+91" + ? "" + : parsePhoneNumber(state.form.phone_number), alt_phone_number: - parsePhoneNumberFromString( + parsePhoneNumber( state.form.phone_number_is_whatsapp - ? state.form.phone_number + ? state.form.phone_number === "+91" + ? "" + : state.form.phone_number + : state.form.alt_phone_number === "+91" + ? "" : state.form.alt_phone_number - )?.format("E.164") ?? "", + ) ?? "", date_of_birth: dateQueryString(state.form.date_of_birth), age: Number(dayjs().diff(state.form.date_of_birth, "years", false)), doctor_qualification: diff --git a/src/Components/Users/UserFilter.tsx b/src/Components/Users/UserFilter.tsx index 681b7517e04..1450e6b6f79 100644 --- a/src/Components/Users/UserFilter.tsx +++ b/src/Components/Users/UserFilter.tsx @@ -3,7 +3,7 @@ import { useDispatch } from "react-redux"; import { getDistrict } from "../../Redux/actions"; import { navigate } from "raviger"; import DistrictSelect from "../Facility/FacilityFilter/DistrictSelect"; -import parsePhoneNumberFromString from "libphonenumber-js"; +import { parsePhoneNumber } from "../../Utils/utils"; import TextFormField from "../Form/FormFields/TextFormField"; import SelectMenuV2 from "../Form/SelectMenuV2"; import { FieldLabel } from "../Form/FormFields/FormField"; @@ -14,8 +14,8 @@ import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; const parsePhoneNumberForFilterParam = (phoneNumber: string) => { if (!phoneNumber) return ""; - if (phoneNumber.startsWith("+")) - return parsePhoneNumberFromString(phoneNumber)?.format("E.164") || ""; + if (phoneNumber === "+91") return ""; + if (phoneNumber.startsWith("+")) return parsePhoneNumber(phoneNumber) ?? ""; return phoneNumber; }; @@ -25,8 +25,8 @@ export default function UserFilter(props: any) { const [filterState, setFilterState] = useMergeState({ first_name: filter.first_name || "", last_name: filter.last_name || "", - phone_number: filter.phone_number || undefined, - alt_phone_number: filter.alt_phone_number || undefined, + phone_number: filter.phone_number || "+91", + alt_phone_number: filter.alt_phone_number || "+91", user_type: filter.user_type || "", district_id: filter.district_id || "", district_ref: null, diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx index 3fb4bccc7c7..5c5ad46b9fa 100644 --- a/src/Components/Users/UserProfile.tsx +++ b/src/Components/Users/UserProfile.tsx @@ -8,13 +8,12 @@ import { partialUpdateUser, updateUserPassword, } from "../../Redux/actions"; -import { parsePhoneNumberFromString } from "libphonenumber-js/max"; import { validateEmailAddress } from "../../Common/validation"; import * as Notification from "../../Utils/Notifications.js"; import LanguageSelector from "../../Components/Common/LanguageSelector"; import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2, { Submit } from "../Common/components/ButtonV2"; -import { classNames, handleSignOut } from "../../Utils/utils"; +import { classNames, handleSignOut, parsePhoneNumber } from "../../Utils/utils"; import CareIcon from "../../CAREUI/icons/CareIcon"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; @@ -23,6 +22,7 @@ import { SkillModel, SkillObjectModel } from "../Users/models"; import UpdatableApp, { checkForUpdate } from "../Common/UpdatableApp"; import dayjs from "../../Utils/dayjs"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import { PhoneNumberValidator } from "../Form/FieldValidators"; const Loading = lazy(() => import("../Common/Loading")); @@ -199,15 +199,12 @@ export default function UserProfile() { return; case "phoneNumber": // eslint-disable-next-line no-case-declarations - const phoneNumber = parsePhoneNumberFromString( - states.form[field], - "IN" - ); + const phoneNumber = parsePhoneNumber(states.form[field]); // eslint-disable-next-line no-case-declarations let is_valid = false; if (phoneNumber) { - is_valid = phoneNumber.isValid(); + is_valid = PhoneNumberValidator()(phoneNumber) === undefined; } if (!states.form[field] || !is_valid) { @@ -219,12 +216,10 @@ export default function UserProfile() { // eslint-disable-next-line no-case-declarations let alt_is_valid = false; if (states.form[field] && states.form[field] !== "+91") { - const altPhoneNumber = parsePhoneNumberFromString( - states.form[field], - "IN" - ); + const altPhoneNumber = parsePhoneNumber(states.form[field]); if (altPhoneNumber) { - alt_is_valid = altPhoneNumber.isValid(); + alt_is_valid = + PhoneNumberValidator(["mobile"])(altPhoneNumber) === undefined; } } @@ -300,13 +295,8 @@ export default function UserProfile() { first_name: states.form.firstName, last_name: states.form.lastName, email: states.form.email, - phone_number: parsePhoneNumberFromString( - states.form.phoneNumber - )?.format("E.164"), - alt_phone_number: - parsePhoneNumberFromString(states.form.altPhoneNumber)?.format( - "E.164" - ) || "", + phone_number: parsePhoneNumber(states.form.phoneNumber) ?? "", + alt_phone_number: parsePhoneNumber(states.form.altPhoneNumber) ?? "", gender: states.form.gender, age: states.form.age, doctor_qualification: diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 0ccf38ee9ce..4c29d5105f3 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -1,5 +1,10 @@ import { navigate } from "raviger"; -import { LocalStorageKeys } from "../Common/constants"; +import { + AREACODES, + IN_LANDLINE_AREA_CODES, + LocalStorageKeys, +} from "../Common/constants"; +import phoneCodesJson from "../Common/static/countryPhoneAndFlags.json"; import dayjs from "./dayjs"; interface ApacheParams { @@ -240,3 +245,135 @@ export const formatCurrency = (price: number) => export const isUserOnline = (user: { last_login: DateLike }) => { return dayjs().subtract(5, "minutes").isBefore(user.last_login); }; + +export interface CountryData { + flag: string; + name: string; + code: string; +} + +export const parsePhoneNumber = (phoneNumber: string, countryCode?: string) => { + const phoneCodes: Record = phoneCodesJson; + let parsedNumber = phoneNumber.replace(/[-+() ]/g, ""); + if (countryCode && phoneCodes[countryCode]) { + parsedNumber = phoneCodes[countryCode].code + parsedNumber; + } else if (!phoneNumber.startsWith("+")) { + return undefined; + } + parsedNumber = "+" + parsedNumber; + return parsedNumber; +}; + +export const formatPhoneNumber = (phoneNumber: string) => { + if (phoneNumber.startsWith("+91")) { + phoneNumber = phoneNumber.startsWith("+910") + ? phoneNumber.slice(4) + : phoneNumber.slice(3); + const landline_code = IN_LANDLINE_AREA_CODES.find((code) => + phoneNumber.startsWith(code) + ); + if (landline_code === undefined) + return "+91" + " " + phoneNumber.slice(0, 5) + " " + phoneNumber.slice(5); + const subscriber_no_length = 10 - landline_code.length; + return ( + "+91" + + " " + + landline_code + + " " + + phoneNumber.slice( + landline_code.length, + subscriber_no_length / 2 + landline_code.length + ) + + " " + + phoneNumber.slice(subscriber_no_length / 2 + landline_code.length) + ); + } else if (phoneNumber.startsWith("1800")) { + return "1800" + " " + phoneNumber.slice(4, 7) + " " + phoneNumber.slice(7); + } else if (phoneNumber.startsWith("+")) { + const countryCode = getCountryCode(phoneNumber); + if (!countryCode) return phoneNumber; + const phoneCodes: Record = phoneCodesJson; + return ( + "+" + + phoneCodes[countryCode].code + + " " + + phoneNumber.slice(phoneCodes[countryCode].code.length + 1) + ); + } + return phoneNumber; +}; + +export const getCountryCode = (phoneNumber: string) => { + if (phoneNumber.startsWith("+")) { + const phoneCodes: Record = phoneCodesJson; + const phoneCodesArr = Object.keys(phoneCodes); + phoneNumber = phoneNumber.slice(1); + const allMatchedCountries: { name: string; code: string }[] = []; + for (let i = 0; i < phoneCodesArr.length; i++) { + if ( + phoneNumber.startsWith( + phoneCodes[phoneCodesArr[i]].code.replaceAll("-", "") + ) + ) { + allMatchedCountries.push({ + name: phoneCodesArr[i], + code: phoneCodes[phoneCodesArr[i]].code.replaceAll("-", ""), + }); + } + } + // returns the country which is longest in case there are multiple matches + if (allMatchedCountries.length === 0) return undefined; + const matchedCountry = allMatchedCountries.reduce((max, country) => + max.code > country.code ? max : country + ); + const sameCodeCountries = allMatchedCountries.filter( + (country) => country.code === matchedCountry.code + ); + if (matchedCountry === undefined) return undefined; + // some countries share same country code but differ in area codes + // The area codes are checked for such countries + if (matchedCountry.code == "1") { + const areaCode = phoneNumber.substring(1, 4); + return ( + sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name ?? "US" + ); + } else if (matchedCountry.code === "262") { + const areaCode = phoneNumber.substring(3, 6); + return sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name; + } else if (matchedCountry.code === "61") { + const areaCode = phoneNumber.substring(2, 7); + return ( + sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name ?? "AU" + ); + } else if (matchedCountry.code === "599") { + const areaCode = phoneNumber.substring(3, 4); + return ( + sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name ?? "CW" + ); + } else if (matchedCountry.code == "7") { + const areaCode = phoneNumber.substring(1, 2); + return ( + sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name ?? "RU" + ); + } else if (matchedCountry.code == "47") { + const areaCode = phoneNumber.substring(2, 4); + return ( + sameCodeCountries.find((country) => + AREACODES[country.name]?.includes(areaCode) + )?.name ?? "NO" + ); + } + return matchedCountry.name; + } + return undefined; +}; From 97ed8ae4a24cd6c78d150bdcb3b871078a9326f7 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Wed, 20 Sep 2023 03:51:09 +0530 Subject: [PATCH 06/25] Improved asset details page (#6256) * improved asset details page * after 2sec the copy button will reappear * changed asset desc position and made the separator evenly spaced --- src/Components/Assets/AssetManage.tsx | 82 ++++++++++----------- src/Components/Assets/AssetWarrantyCard.tsx | 40 +++++++++- 2 files changed, 77 insertions(+), 45 deletions(-) diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 1df9fbde9a6..27f0f2d8bc0 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -26,7 +26,6 @@ import { UserRole, USER_TYPES } from "../../Common/constants"; import ConfirmDialog from "../Common/ConfirmDialog"; import RecordMeta from "../../CAREUI/display/RecordMeta"; import { useTranslation } from "react-i18next"; -const PageTitle = lazy(() => import("../Common/PageTitle")); const Loading = lazy(() => import("../Common/Loading")); import * as Notification from "../../Utils/Notifications.js"; import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -35,6 +34,7 @@ import useAuthUser from "../../Common/hooks/useAuthUser"; import dayjs from "dayjs"; import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import { AssetServiceEditModal } from "./AssetServiceEditModal"; +import Page from "../Common/components/Page"; interface AssetManageProps { assetId: string; @@ -329,18 +329,28 @@ const AssetManage = (props: AssetManageProps) => { }; return ( -
- + + + Export as JSON + + } + > { {asset?.name} - - - - Export as JSON - - +
+ + {assetClassProp.name} +
+
+
+ {asset?.description}
+ {asset?.asset_type === "INTERNAL" ? ( + + ) : ( + + )} {asset?.status === "ACTIVE" ? ( ) : ( @@ -394,7 +405,9 @@ const AssetManage = (props: AssetManageProps) => { )}
- {asset?.description} +
+ {asset?.description} +
{[ @@ -403,19 +416,6 @@ const AssetManage = (props: AssetManageProps) => { icon: "location-pin-alt", content: asset?.location_object.name, }, - { - label: "Asset Type", - icon: "apps", - content: - asset?.asset_type === "INTERNAL" - ? "Internal Asset" - : "External Asset", - }, - { - label: "Asset Class", - icon: assetClassProp.icon, - content: assetClassProp.name, - }, { label: "Asset QR Code ID", icon: "qrcode-scan", @@ -586,7 +586,7 @@ const AssetManage = (props: AssetManageProps) => { viewOnly={serviceEditData.viewOnly} /> )} -
+ ); }; diff --git a/src/Components/Assets/AssetWarrantyCard.tsx b/src/Components/Assets/AssetWarrantyCard.tsx index 5d10d4096c8..2e5fa74b9c6 100644 --- a/src/Components/Assets/AssetWarrantyCard.tsx +++ b/src/Components/Assets/AssetWarrantyCard.tsx @@ -1,36 +1,68 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import { AssetData } from "./AssetTypes"; import { classNames, formatDate } from "../../Utils/utils"; +import CopyToClipboard from "react-copy-to-clipboard"; +import { t } from "i18next"; +import { useEffect, useState } from "react"; export default function AssetWarrantyCard(props: { asset: AssetData }) { const { asset } = props; const details = { "Serial Number": asset.serial_number, - Expiry: + "Warranty/AMC Expiry": asset.warranty_amc_end_of_validity && formatDate(asset.warranty_amc_end_of_validity), Vendor: asset.vendor_name, }; + const [isCopied, setIsCopied] = useState(false); + + useEffect(() => { + if (isCopied) { + const timeout = setTimeout(() => { + setIsCopied(false); + }, 2000); + return () => clearTimeout(timeout); + } + }, [isCopied]); + return (
{asset.manufacturer}
-
-
+
+
{Object.keys(details).map((key) => (
{key}
-
+
{details[key as keyof typeof details] || "--"} + {key === "Serial Number" && ( + + )}
))}
+
From eb09ecd41b31a09d41c37acb688ab52238662959 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:49:06 +0530 Subject: [PATCH 07/25] Activate asset config for district/state admins only (#6309) --- src/Components/Assets/AssetManage.tsx | 3 +- .../Assets/AssetType/HL7Monitor.tsx | 71 ++++++------ .../Assets/AssetType/ONVIFCamera.tsx | 107 +++++++++--------- 3 files changed, 93 insertions(+), 88 deletions(-) diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 27f0f2d8bc0..4565c48b33d 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -28,7 +28,7 @@ import RecordMeta from "../../CAREUI/display/RecordMeta"; import { useTranslation } from "react-i18next"; const Loading = lazy(() => import("../Common/Loading")); import * as Notification from "../../Utils/Notifications.js"; -import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import Uptime from "../Common/Uptime"; import useAuthUser from "../../Common/hooks/useAuthUser"; import dayjs from "dayjs"; @@ -453,7 +453,6 @@ const AssetManage = (props: AssetManageProps) => { } id="configure-asset" data-testid="asset-configure-button" - authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} > {t("configure")} diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index b19190ed410..55f4d0c258e 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -15,6 +15,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import TextFormField from "../../Form/FormFields/TextFormField"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; +import useAuthUser from "../../../Common/hooks/useAuthUser"; interface HL7MonitorProps { assetId: string; @@ -31,7 +32,7 @@ const HL7Monitor = (props: HL7MonitorProps) => { const [isLoading, setIsLoading] = useState(true); const [localipAddress, setLocalIPAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); - + const authUser = useAuthUser(); const dispatch = useDispatch(); useEffect(() => { @@ -87,40 +88,42 @@ const HL7Monitor = (props: HL7MonitorProps) => { return (
-
- -
-

Connection

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

Connection

+
+ setMiddlewareHostname(e.value)} + errorClassName="hidden" + /> + setLocalIPAddress(e.value)} + required + error={ipadrdress_error} + /> + + + Save Configuration + +
+
- )} -
+ {["HL7MONITOR"].includes(assetType) && ( + + + + )} +
+ )} {assetType === "HL7MONITOR" && ( { ); const [refreshHash, setRefreshHash] = useState(Number(new Date())); const dispatch = useDispatch(); - + const authUser = useAuthUser(); useEffect(() => { const fetchFacility = async () => { const res = await dispatch(getPermittedFacility(facilityId)); @@ -147,57 +148,59 @@ const ONVIFCamera = (props: ONVIFCameraProps) => { return (
-
-
- setMiddlewareHostname(value)} - /> - setCameraAddress(value)} - error={ipadrdress_error} - /> - setUsername(value)} - /> - setPassword(value)} - /> - setStreamUuid(value)} - /> -
-
- -
-
+ {["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) && ( +
+
+ setMiddlewareHostname(value)} + /> + setCameraAddress(value)} + error={ipadrdress_error} + /> + setUsername(value)} + /> + setPassword(value)} + /> + setStreamUuid(value)} + /> +
+
+ +
+
+ )} {assetType === "ONVIF" ? ( Date: Wed, 20 Sep 2023 18:49:40 +0530 Subject: [PATCH 08/25] Enhanced UI for Principle diagnosis (#6264) * highlight principal diagnosis * refactor * use chip * add badge * add tooltip --- .../Facility/ConsultationDetails/index.tsx | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 490c704f295..3d9fa0f6e75 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -8,7 +8,7 @@ import { ConsultationModel, ICD11DiagnosisModel } from "../models"; import { getConsultation, getPatient } from "../../../Redux/actions"; import { statusType, useAbortableEffect } from "../../../Common/utils"; import { lazy, useCallback, useState } from "react"; - +import ToolTip from "../../Common/utils/Tooltip"; import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import DischargeModal from "../DischargeModal"; @@ -191,10 +191,20 @@ export const ConsultationDetails = (props: any) => { return diagnoses.length ? (

{label}

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

{diagnosis.label}

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

{diagnosis.label}

+
+ + + +
+
+ ) : ( +

{diagnosis.label}

+ ) + )} {diagnoses.length > nshow && ( <> {!showMore ? ( @@ -359,22 +369,6 @@ export const ConsultationDetails = (props: any) => {
)*/} - {consultationData.icd11_principal_diagnosis && ( - - d.id === consultationData.icd11_principal_diagnosis - )!, - ]} - /> - )} - Date: Wed, 20 Sep 2023 20:55:30 +0530 Subject: [PATCH 09/25] Removed react-swipeable-views package (#6312) * removed SwipeableViews from ManagePatients.tsx * Removed react-swipeable-views package --- package-lock.json | 137 +--------------------- package.json | 2 - src/Components/Patient/ManagePatients.tsx | 15 +-- 3 files changed, 7 insertions(+), 147 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f655932fd7..4871fbe374b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "react-player": "^2.12.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", - "react-swipeable-views": "^0.14.0", "react-transition-group": "^4.4.5", "react-webcam": "^7.1.1", "read-excel-file": "^5.6.1", @@ -85,7 +84,6 @@ "@types/react-dom": "^18.2.6", "@types/react-google-recaptcha": "^2.1.5", "@types/react-qr-reader": "^2.1.4", - "@types/react-swipeable-views": "^0.13.2", "@types/react-transition-group": "^4.4.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", @@ -5639,15 +5637,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-swipeable-views": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.13.2.tgz", - "integrity": "sha512-FiszBm9M0JicAgzO/IwDqpfHQRUEjPZA88UexYsVD6qHJBf5LrbGjR5Mw4+yZbf8ZxJneNqOsZbe4WGjOYG7iQ==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", @@ -11996,11 +11985,6 @@ "node": ">=4.0" } }, - "node_modules/keycode": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", - "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -14273,6 +14257,7 @@ "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -15267,15 +15252,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/npm/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -18125,19 +18101,6 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, - "node_modules/react-event-listener": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", - "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": "^16.3.0" - } - }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -18306,91 +18269,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-swipeable-views": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.14.0.tgz", - "integrity": "sha512-wrTT6bi2nC3JbmyNAsPXffUXLn0DVT9SbbcFr36gKpbaCgEp7rX/OFxsu5hPc/NBsUhHyoSRGvwqJNNrWTwCww==", - "dependencies": { - "@babel/runtime": "7.0.0", - "prop-types": "^15.5.4", - "react-swipeable-views-core": "^0.14.0", - "react-swipeable-views-utils": "^0.14.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/react-swipeable-views-core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.14.0.tgz", - "integrity": "sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA==", - "dependencies": { - "@babel/runtime": "7.0.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/react-swipeable-views-core/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views-core/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, - "node_modules/react-swipeable-views-utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.14.0.tgz", - "integrity": "sha512-W+fXBOsDqgFK1/g7MzRMVcDurp3LqO3ksC8UgInh2P/tKgb5DusuuB1geKHFc6o1wKl+4oyER4Zh3Lxmr8xbXA==", - "dependencies": { - "@babel/runtime": "7.0.0", - "keycode": "^2.1.7", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.0", - "react-swipeable-views-core": "^0.14.0", - "shallow-equal": "^1.2.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/react-swipeable-views-utils/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views-utils/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, - "node_modules/react-swipeable-views/node_modules/@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/react-swipeable-views/node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -19371,11 +19249,6 @@ "node": ">=8" } }, - "node_modules/shallow-equal": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", - "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -21268,14 +21141,6 @@ "makeerror": "1.0.12" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 8ade8c604be..246734dc3d4 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "react-player": "^2.12.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", - "react-swipeable-views": "^0.14.0", "react-transition-group": "^4.4.5", "react-webcam": "^7.1.1", "read-excel-file": "^5.6.1", @@ -125,7 +124,6 @@ "@types/react-dom": "^18.2.6", "@types/react-google-recaptcha": "^2.1.5", "@types/react-qr-reader": "^2.1.4", - "@types/react-swipeable-views": "^0.13.2", "@types/react-transition-group": "^4.4.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 71a3ad08c8c..e15db9f3b40 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -36,7 +36,6 @@ import RecordMeta from "../../CAREUI/display/RecordMeta"; import SearchInput from "../Form/SearchInput"; import SortDropdownMenu from "../Common/SortDropdown"; import SwitchTabs from "../Common/components/SwitchTabs"; -import SwipeableViews from "react-swipeable-views"; import { parseOptionId } from "../../Common/utils"; import { parsePhoneNumber } from "../../Utils/utils.js"; import { useDispatch } from "react-redux"; @@ -985,14 +984,12 @@ export const PatientManager = () => {
- - -
{managePatients}
-
- -
{managePatients}
-
-
+ +
{managePatients}
+
+ +
{managePatients}
+
Date: Thu, 21 Sep 2023 10:07:59 +0530 Subject: [PATCH 10/25] Refactor `App.tsx`, Providers, Integrations; Remove usage on redux's states; Adds support for notifications to `useQuery` and `request` (#6287) * Refactor `App.tsx`, providers, integrations and remove redux state usage. * remove axios dependency * fixes trailing slash redirect for token refresh * handle notifications * fix auth state management and notifications * fix suspense fallback * support for reattempts * request; make new headers on every attempt --- src/App.tsx | 127 +++--------------- src/CAREUI/misc/ThemedFavicon.tsx | 14 ++ src/Common/hooks/useAppHistory.ts | 2 +- src/Common/hooks/useFilters.tsx | 2 +- .../Facility/ConsultationDetails/index.tsx | 2 +- .../Facility/Consultations/Feed.tsx | 2 +- src/Components/Patient/ManagePatients.tsx | 2 +- src/Components/Patient/PatientHome.tsx | 2 +- .../VitalsMonitor/HL7PatientVitalsMonitor.tsx | 2 +- .../Common => Integrations}/Plausible.tsx | 4 +- src/Integrations/Sentry.tsx | 25 ++++ src/Integrations/index.tsx | 6 + src/Providers/AppConfigProvider.tsx | 25 ++++ src/Providers/AuthUserProvider.tsx | 63 +++++++++ .../misc => Providers}/HistoryAPIProvider.tsx | 4 +- src/Providers/index.tsx | 5 + src/Redux/api.tsx | 12 +- src/{Router => Routers}/AppRouter.tsx | 0 src/{Router => Routers}/SessionRouter.tsx | 0 src/Routers/index.tsx | 6 + src/Utils/request/handleResponse.ts | 38 ++++++ src/Utils/request/request.ts | 54 ++++++-- src/Utils/request/types.ts | 11 +- src/Utils/request/useQuery.ts | 24 +--- src/Utils/request/utils.ts | 9 ++ 25 files changed, 295 insertions(+), 146 deletions(-) create mode 100644 src/CAREUI/misc/ThemedFavicon.tsx rename src/{Components/Common => Integrations}/Plausible.tsx (95%) create mode 100644 src/Integrations/Sentry.tsx create mode 100644 src/Integrations/index.tsx create mode 100644 src/Providers/AppConfigProvider.tsx create mode 100644 src/Providers/AuthUserProvider.tsx rename src/{CAREUI/misc => Providers}/HistoryAPIProvider.tsx (93%) create mode 100644 src/Providers/index.tsx rename src/{Router => Routers}/AppRouter.tsx (100%) rename src/{Router => Routers}/SessionRouter.tsx (100%) create mode 100644 src/Routers/index.tsx create mode 100644 src/Utils/request/handleResponse.ts diff --git a/src/App.tsx b/src/App.tsx index e00ca18f652..f89dfd11e74 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,111 +1,28 @@ -import * as Sentry from "@sentry/browser"; - -import { FC, Suspense, lazy, useEffect, useState } from "react"; -import { getConfig, getCurrentUser } from "./Redux/actions"; -import { statusType, useAbortableEffect } from "./Common/utils"; -import { useDispatch, useSelector } from "react-redux"; - -import AppRouter from "./Router/AppRouter"; -import { HistoryAPIProvider } from "./CAREUI/misc/HistoryAPIProvider"; -import { AppConfigContext, IConfig } from "./Common/hooks/useConfig"; -import { LocalStorageKeys } from "./Common/constants"; -import Plausible from "./Components/Common/Plausible"; -import SessionRouter from "./Router/SessionRouter"; -import axios from "axios"; -import { AuthUserContext } from "./Common/hooks/useAuthUser"; - -const Loading = lazy(() => import("./Components/Common/Loading")); - -const App: FC = () => { - const dispatch: any = useDispatch(); - const state: any = useSelector((state) => state); - const { currentUser, config } = state; - const [user, setUser] = useState(null); - - useAbortableEffect(async () => { - const res = await dispatch(getConfig()); - if (res.data && res.status < 400) { - const config = res.data as IConfig; - - if (config?.sentry_dsn && import.meta.env.PROD) { - Sentry.init({ - environment: config.sentry_environment, - dsn: config.sentry_dsn, - }); - } - - localStorage.setItem("config", JSON.stringify(config)); - } - }, [dispatch]); - - const updateRefreshToken = () => { - const refresh = localStorage.getItem(LocalStorageKeys.refreshToken); - // const access = localStorage.getItem(LocalStorageKeys.accessToken); - // if (!access && refresh) { - // localStorage.removeItem(LocalStorageKeys.refreshToken); - // document.location.reload(); - // return; - // } - if (!refresh) { - return; - } - axios - .post("/api/v1/auth/token/refresh/", { - refresh, - }) - .then((resp) => { - localStorage.setItem(LocalStorageKeys.accessToken, resp.data.access); - localStorage.setItem(LocalStorageKeys.refreshToken, resp.data.refresh); - }); - }; - useEffect(() => { - updateRefreshToken(); - setInterval(updateRefreshToken, 5 * 60 * 1000); - }, [user]); - - useAbortableEffect( - async (status: statusType) => { - const res = await dispatch(getCurrentUser()); - if (!status.aborted && res && res.statusCode === 200) { - setUser(res.data); - } - }, - [dispatch] - ); - - useEffect(() => { - const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); - const favicon: any = document.querySelector("link[rel~='icon']"); - if (darkThemeMq.matches) { - favicon.href = "/favicon-light.ico"; - } else { - favicon.href = "/favicon.ico"; - } - }, []); - - if ( - !currentUser || - currentUser.isFetching || - !config || - config.isFetching || - !config.data - ) { - return ; - } - +import { Suspense } from "react"; +import Routers from "./Routers"; +import { + AppConfigProvider, + AuthUserProvider, + HistoryAPIProvider, +} from "./Providers"; +import ThemedFavicon from "./CAREUI/misc/ThemedFavicon"; +import Intergrations from "./Integrations"; +import Loading from "./Components/Common/Loading"; + +const App = () => { return ( }> + - - {currentUser?.data ? ( - - - - ) : ( - - )} - - + + }> + + + + {/* Integrations */} + + + ); diff --git a/src/CAREUI/misc/ThemedFavicon.tsx b/src/CAREUI/misc/ThemedFavicon.tsx new file mode 100644 index 00000000000..55b8d34ec7e --- /dev/null +++ b/src/CAREUI/misc/ThemedFavicon.tsx @@ -0,0 +1,14 @@ +import { useEffect } from "react"; + +export default function ThemedFavicon() { + useEffect(() => { + const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); + const favicon = document.querySelector( + "link[rel~='icon']" + ) as HTMLLinkElement; + + favicon.href = darkThemeMq.matches ? "/favicon-light.ico" : "/favicon.ico"; + }, []); + + return null; +} diff --git a/src/Common/hooks/useAppHistory.ts b/src/Common/hooks/useAppHistory.ts index f4ee2f11f78..a4605db082b 100644 --- a/src/Common/hooks/useAppHistory.ts +++ b/src/Common/hooks/useAppHistory.ts @@ -3,7 +3,7 @@ import { useContext } from "react"; import { HistoryContext, ResetHistoryContext, -} from "../../CAREUI/misc/HistoryAPIProvider"; +} from "../../Providers/HistoryAPIProvider"; export default function useAppHistory() { const history = useContext(HistoryContext); diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx index 309cdc37787..129a696041b 100644 --- a/src/Common/hooks/useFilters.tsx +++ b/src/Common/hooks/useFilters.tsx @@ -196,7 +196,7 @@ export default function useFilters({ limit = 14 }: { limit?: number }) { FilterBadge, FilterBadges, Pagination, - // TODO: update this props to be compliant with new FiltersSlideOver when #3996 is merged. + advancedFilter: { show: showFilters, setShow: setShowFilters, diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 3d9fa0f6e75..356257f9ac6 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -23,7 +23,7 @@ import { navigate } from "raviger"; import { useDispatch } from "react-redux"; import { useQueryParams } from "raviger"; import { useTranslation } from "react-i18next"; -import { triggerGoal } from "../../Common/Plausible"; +import { triggerGoal } from "../../../Integrations/Plausible"; import useAuthUser from "../../../Common/hooks/useAuthUser"; import { ConsultationUpdatesTab } from "./ConsultationUpdatesTab"; import { ConsultationABGTab } from "./ConsultationABGTab"; diff --git a/src/Components/Facility/Consultations/Feed.tsx b/src/Components/Facility/Consultations/Feed.tsx index 84b3e62ef9e..75ab72d168c 100644 --- a/src/Components/Facility/Consultations/Feed.tsx +++ b/src/Components/Facility/Consultations/Feed.tsx @@ -30,7 +30,7 @@ import { useDispatch } from "react-redux"; import { useHLSPLayer } from "../../../Common/hooks/useHLSPlayer"; import useKeyboardShortcut from "use-keyboard-shortcut"; import useFullscreen from "../../../Common/hooks/useFullscreen.js"; -import { triggerGoal } from "../../Common/Plausible.js"; +import { triggerGoal } from "../../../Integrations/Plausible.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; interface IFeedProps { diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index e15db9f3b40..423edb894c9 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -43,7 +43,7 @@ import useFilters from "../../Common/hooks/useFilters"; import { useTranslation } from "react-i18next"; import Page from "../Common/components/Page.js"; import dayjs from "dayjs"; -import { triggerGoal } from "../Common/Plausible.js"; +import { triggerGoal } from "../../Integrations/Plausible.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; const Loading = lazy(() => import("../Common/Loading")); diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 3d39f377462..8fdc0f51280 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -30,7 +30,7 @@ import Page from "../Common/components/Page"; import ConfirmDialog from "../Common/ConfirmDialog"; import UserAutocompleteFormField from "../Common/UserAutocompleteFormField"; import dayjs from "../../Utils/dayjs"; -import { triggerGoal } from "../Common/Plausible"; +import { triggerGoal } from "../../Integrations/Plausible"; import useAuthUser from "../../Common/hooks/useAuthUser"; const Loading = lazy(() => import("../Common/Loading")); diff --git a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx index c0b089df8b0..1b45fd80ddc 100644 --- a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx +++ b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx @@ -6,7 +6,7 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import WaveformLabels from "./WaveformLabels"; import { classNames } from "../../Utils/utils"; import { IVitalsComponentProps, VitalsValueBase } from "./types"; -import { triggerGoal } from "../Common/Plausible"; +import { triggerGoal } from "../../Integrations/Plausible"; import useAuthUser from "../../Common/hooks/useAuthUser"; export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { diff --git a/src/Components/Common/Plausible.tsx b/src/Integrations/Plausible.tsx similarity index 95% rename from src/Components/Common/Plausible.tsx rename to src/Integrations/Plausible.tsx index 77589e0f923..c9b040749d4 100644 --- a/src/Components/Common/Plausible.tsx +++ b/src/Integrations/Plausible.tsx @@ -1,6 +1,6 @@ import { useLocationChange } from "raviger"; -import useConfig from "../../Common/hooks/useConfig"; -import Script from "./Script"; +import useConfig from "../Common/hooks/useConfig"; +import Script from "../Components/Common/Script"; import { useEffect } from "react"; export default function Plausible() { diff --git a/src/Integrations/Sentry.tsx b/src/Integrations/Sentry.tsx new file mode 100644 index 00000000000..466f93487c4 --- /dev/null +++ b/src/Integrations/Sentry.tsx @@ -0,0 +1,25 @@ +import { useEffect } from "react"; +import useConfig from "../Common/hooks/useConfig"; + +interface Props { + disabled?: boolean; +} + +export default function Sentry({ disabled }: Props) { + const { sentry_dsn, sentry_environment } = useConfig(); + + useEffect(() => { + if (disabled || !sentry_dsn) { + return; + } + + import("@sentry/browser").then((Sentry) => { + Sentry.init({ + environment: sentry_environment, + dsn: sentry_dsn, + }); + }); + }, [sentry_dsn, sentry_environment, disabled]); + + return null; +} diff --git a/src/Integrations/index.tsx b/src/Integrations/index.tsx new file mode 100644 index 00000000000..aeb0399a452 --- /dev/null +++ b/src/Integrations/index.tsx @@ -0,0 +1,6 @@ +import Sentry from "./Sentry"; +import Plausible from "./Plausible"; + +const Intergrations = { Sentry, Plausible }; + +export default Intergrations; diff --git a/src/Providers/AppConfigProvider.tsx b/src/Providers/AppConfigProvider.tsx new file mode 100644 index 00000000000..7492a83c126 --- /dev/null +++ b/src/Providers/AppConfigProvider.tsx @@ -0,0 +1,25 @@ +import { AppConfigContext } from "../Common/hooks/useConfig"; +import Loading from "../Components/Common/Loading"; +import routes from "../Redux/api"; +import useQuery from "../Utils/request/useQuery"; + +interface Props { + children: React.ReactNode; +} + +export default function AppConfigProvider({ children }: Props) { + const { data, loading } = useQuery(routes.config, { + refetchOnWindowFocus: false, + prefetch: true, + }); + + if (loading || data === undefined) { + return ; + } + + return ( + + {children} + + ); +} diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx new file mode 100644 index 00000000000..64027a17215 --- /dev/null +++ b/src/Providers/AuthUserProvider.tsx @@ -0,0 +1,63 @@ +import { useEffect } from "react"; +import { AuthUserContext } from "../Common/hooks/useAuthUser"; +import Loading from "../Components/Common/Loading"; +import routes from "../Redux/api"; +import useQuery from "../Utils/request/useQuery"; +import { LocalStorageKeys } from "../Common/constants"; +import request from "../Utils/request/request"; + +interface Props { + children: React.ReactNode; + unauthorized: React.ReactNode; +} + +export default function AuthUserProvider({ children, unauthorized }: Props) { + const { res, data, loading } = useQuery(routes.currentUser, { + refetchOnWindowFocus: false, + prefetch: true, + silent: true, + }); + + useEffect(() => { + if (!data) { + return; + } + + updateRefreshToken(true); + setInterval(() => updateRefreshToken(), 5 * 60 * 1000); // TODO: move this interval to config.json + }, [data]); + + if (loading || !res) { + return ; + } + + if (res.status !== 200 || !data) { + return unauthorized; + } + + return ( + {children} + ); +} + +const updateRefreshToken = async (silent = false) => { + const refresh = localStorage.getItem(LocalStorageKeys.refreshToken); + + if (!refresh) { + return; + } + + const { res, data } = await request(routes.token_refresh, { + body: { refresh }, + silent, + }); + + if (res?.status !== 200 || !data) { + localStorage.removeItem(LocalStorageKeys.accessToken); + localStorage.removeItem(LocalStorageKeys.refreshToken); + return; + } + + localStorage.setItem(LocalStorageKeys.accessToken, data.access); + localStorage.setItem(LocalStorageKeys.refreshToken, data.refresh); +}; diff --git a/src/CAREUI/misc/HistoryAPIProvider.tsx b/src/Providers/HistoryAPIProvider.tsx similarity index 93% rename from src/CAREUI/misc/HistoryAPIProvider.tsx rename to src/Providers/HistoryAPIProvider.tsx index e82e379053f..3fa4b6fab86 100644 --- a/src/CAREUI/misc/HistoryAPIProvider.tsx +++ b/src/Providers/HistoryAPIProvider.tsx @@ -5,7 +5,7 @@ export const HistoryContext = createContext([]); // eslint-disable-next-line @typescript-eslint/no-empty-function export const ResetHistoryContext = createContext(() => {}); -export const HistoryAPIProvider = (props: { children: ReactNode }) => { +export default function HistoryAPIProvider(props: { children: ReactNode }) { const [history, setHistory] = useState([]); useLocationChange( @@ -36,4 +36,4 @@ export const HistoryAPIProvider = (props: { children: ReactNode }) => { ); -}; +} diff --git a/src/Providers/index.tsx b/src/Providers/index.tsx new file mode 100644 index 00000000000..728ee9e1f91 --- /dev/null +++ b/src/Providers/index.tsx @@ -0,0 +1,5 @@ +import AppConfigProvider from "./AppConfigProvider"; +import AuthUserProvider from "./AuthUserProvider"; +import HistoryAPIProvider from "./HistoryAPIProvider"; + +export { AppConfigProvider, AuthUserProvider, HistoryAPIProvider }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index b98a099f439..cb124b5bb80 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,4 +1,6 @@ +import { IConfig } from "../Common/hooks/useConfig"; import { LocationModel } from "../Components/Facility/models"; +import { UserModel } from "../Components/Users/models"; import { PaginatedResponse } from "../Utils/request/types"; /** @@ -9,11 +11,17 @@ function Res(): T { return {} as T; } +interface JwtTokenObtainPair { + access: string; + refresh: string; +} + const routes = { config: { path: import.meta.env.REACT_APP_CONFIG ?? "/config.json", method: "GET", noAuth: true, + TRes: Res(), }, // Auth Endpoints @@ -24,8 +32,9 @@ const routes = { }, token_refresh: { - path: "/api/v1/auth/token/refresh", + path: "/api/v1/auth/token/refresh/", method: "POST", + TRes: Res(), }, token_verify: { @@ -55,6 +64,7 @@ const routes = { // User Endpoints currentUser: { path: "/api/v1/users/getcurrentuser/", + TRes: Res(), }, userList: { diff --git a/src/Router/AppRouter.tsx b/src/Routers/AppRouter.tsx similarity index 100% rename from src/Router/AppRouter.tsx rename to src/Routers/AppRouter.tsx diff --git a/src/Router/SessionRouter.tsx b/src/Routers/SessionRouter.tsx similarity index 100% rename from src/Router/SessionRouter.tsx rename to src/Routers/SessionRouter.tsx diff --git a/src/Routers/index.tsx b/src/Routers/index.tsx new file mode 100644 index 00000000000..8a27df7b59d --- /dev/null +++ b/src/Routers/index.tsx @@ -0,0 +1,6 @@ +import SessionRouter from "./SessionRouter"; +import AppRouter from "./AppRouter"; + +const routers = { SessionRouter, AppRouter }; + +export default routers; diff --git a/src/Utils/request/handleResponse.ts b/src/Utils/request/handleResponse.ts new file mode 100644 index 00000000000..2ecad95ac88 --- /dev/null +++ b/src/Utils/request/handleResponse.ts @@ -0,0 +1,38 @@ +import { RequestResult } from "./types"; +import * as Notifications from "../Notifications"; +import { navigate } from "raviger"; + +export default function handleResponse( + { res, error }: RequestResult, + silent?: boolean +) { + const notify = silent ? undefined : Notifications; + + if (res === undefined) { + return; + } + + // 5xx errors + if (res.status >= 500 && res.status < 600) { + console.error("Server error", res); + notify?.Error({ msg: "Something went wrong...!" }); + return; + } + + // 400/406 Bad Request + if (res.status === 400 || res.status === 406) { + notify?.BadRequest({ errs: error }); + return; + } + + // Other 400 Errors + if (res.status >= 400) { + // Invalid token + if (!silent && error?.code === "token_not_valid") { + navigate("/session-expired"); + } + + notify?.Error({ msg: error?.detail || "Something went wrong...!" }); + return; + } +} diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index 2dc938fa6f1..45c8e5ce664 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -1,4 +1,5 @@ -import { RequestOptions, Route } from "./types"; +import handleResponse from "./handleResponse"; +import { RequestOptions, RequestResult, Route } from "./types"; import { makeHeaders, makeUrl } from "./utils"; interface Options extends RequestOptions { @@ -7,21 +8,56 @@ interface Options extends RequestOptions { export default async function request( { path, method, noAuth }: Route, - { query, body, pathParams, controller }: Options = {} -) { + { + query, + body, + pathParams, + controller, + onResponse, + silent, + reattempts = 3, + }: Options = {} +): Promise> { const signal = controller?.signal; - - const headers = makeHeaders(noAuth ?? false); const url = makeUrl(path, query, pathParams); - const options: RequestInit = { headers, method, signal }; + const options: RequestInit = { method, signal }; if (body) { options.body = JSON.stringify(body); } - const res = await fetch(url, options); - const data: TData = await res.json(); + let result: RequestResult = { + res: undefined, + data: undefined, + error: undefined, + }; + + for (let i = 0; i < reattempts + 1; i++) { + options.headers = makeHeaders(noAuth ?? false); + + try { + const res = await fetch(url, options); + const data: TData = await res.json(); + + result = { + res, + data: res.ok ? data : undefined, + error: res.ok ? undefined : (data as Record), + }; + + onResponse?.(result); + handleResponse(result, silent); + + return result; + } catch (error: any) { + result = { error, res: undefined, data: undefined }; + } + } - return { res, data }; + console.error( + `Request failed after ${reattempts + 1} attempts`, + result.error + ); + return result; } diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index e7f0f9544a3..7f01ef91d9c 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -18,10 +18,19 @@ export interface MutationRoute extends RouteBase { export type Route = QueryRoute | MutationRoute; -export interface RequestOptions { +export interface RequestResult { + res: Response | undefined; + data: TData | undefined; + error: undefined | Record; +} + +export interface RequestOptions { query?: QueryParams; body?: object; pathParams?: Record; + onResponse?: (res: RequestResult) => void; + silent?: boolean; + reattempts?: number; } export interface PaginatedResponse { diff --git a/src/Utils/request/useQuery.ts b/src/Utils/request/useQuery.ts index e459a579e3e..a370b909fe9 100644 --- a/src/Utils/request/useQuery.ts +++ b/src/Utils/request/useQuery.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { QueryRoute, RequestOptions } from "./types"; +import { QueryRoute, RequestOptions, RequestResult } from "./types"; import request from "./request"; import { mergeRequestOptions } from "./utils"; @@ -12,9 +12,7 @@ export default function useQuery( route: QueryRoute, options?: QueryOptions ) { - const [res, setRes] = useState(); - const [data, setData] = useState(); - const [error, setError] = useState(); + const [response, setResponse] = useState>(); const [loading, setLoading] = useState(false); const controllerRef = useRef(); @@ -32,20 +30,8 @@ export default function useQuery( : options; setLoading(true); - - try { - const { res, data } = await request(route, resolvedOptions); - - setRes(res); - setData(res.ok ? data : undefined); - setError(res.ok ? undefined : data); - } catch (error) { - console.error(error); - setData(undefined); - setError(error); - } finally { - setLoading(false); - } + setResponse(await request(route, resolvedOptions)); + setLoading(false); }, [route, JSON.stringify(options)] ); @@ -66,5 +52,5 @@ export default function useQuery( } }, [runQuery, options?.refetchOnWindowFocus]); - return { res, data, error, loading, refetch: runQuery }; + return { ...response, loading, refetch: runQuery }; } diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index 21236a8145f..5a4fb7e2b2e 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -74,8 +74,17 @@ export function mergeRequestOptions( overrides: RequestOptions ): RequestOptions { return { + ...options, + ...overrides, + query: { ...options.query, ...overrides.query }, body: { ...options.body, ...overrides.body }, pathParams: { ...options.pathParams, ...overrides.pathParams }, + + onResponse: (res) => { + options.onResponse?.(res); + overrides.onResponse?.(res); + }, + silent: overrides.silent || options.silent, }; } From 35de3f107c1432acaa4481572a191fa6714b8869 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 21 Sep 2023 10:50:09 +0530 Subject: [PATCH 11/25] Added documentation on `request` and `useQuery` (#6315) --- src/Utils/request/README.md | 142 ++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/Utils/request/README.md diff --git a/src/Utils/request/README.md b/src/Utils/request/README.md new file mode 100644 index 00000000000..a9948d48f41 --- /dev/null +++ b/src/Utils/request/README.md @@ -0,0 +1,142 @@ +# CARE's data fetching utilities: `useQuery` and `request` + +There are two main ways to fetch data in CARE: `useQuery` and `request`. Both of these utilities are built on top of [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). + +## `useQuery` + +`useQuery` is a React hook that allows you to fetch data and automatically update the UI when the data changes. It is +a wrapper around `request` that is designed to be used in React components. Only "GET" requests are supported with `useQuery`. For other request methods (mutations), use `request`. + +### Usage + +```jsx +import { useQuery } from "@care/request"; +import FooRoutes from "@foo/routes"; + +export default function FooDetails({ children, id }) { + const { res, data, loading, error } = useQuery(FooRoutes.getFoo, { + pathParams: { id }, + }); + + /* ๐Ÿช„ Here typeof data is automatically inferred from the specified route. */ + + if (loading) return ; + + if (res.status === 403) { + navigate("/forbidden"); + return null; + } + + if (error) { + return ; + } + + return ( +
+ {data.id} + {data.name} +
+ ); +} +``` + +### API + +```ts +useQuery(route: Route, options?: QueryOptions): ReturnType; +``` + +#### `route` + +A route object that specifies the endpoint to fetch data from. + +```ts +const FooRoutes = { + getFoo: { + path: "/api/v1/foo/{id}/", // ๐Ÿ‘ˆ The path to the endpoint. Slug parameters can be specified using curly braces. + + method: "GET", // ๐Ÿ‘ˆ The HTTP method to use. Optional; defaults to "GET". + TRes: Res, // ๐Ÿ‘ˆ The type of the response body (for type inference). + noAuth: true, // ๐Ÿ‘ˆ Whether to skip adding the Authorization header to the request. + }, +} as const; // ๐Ÿ‘ˆ This is important for type inference to work properly. +``` + +#### `options` + +An object that specifies options for the request. + +```ts +const options = { + prefetch: true, // ๐Ÿ‘ˆ Whether to prefetch the data when the component mounts. + refetchOnWindowFocus: true, // ๐Ÿ‘ˆ Whether to refetch the data when the window regains focus. + + // The following options are passed directly to the underlying `request` function. + + pathParams: { id: "123" }, // ๐Ÿ‘ˆ The slug parameters to use in the path. + // If you accidentally forget to specify a slug parameter an error will be + // thrown before the request is made. + + query: { limit: 10 }, // ๐Ÿ‘ˆ The query parameters to be added to the request URL. + body: { name: "foo" }, // ๐Ÿ‘ˆ The body to be sent with the request. + headers: { "X-Foo": "bar" }, // ๐Ÿ‘ˆ Additional headers to be sent with the request. (Coming soon...) + + silent: true, // ๐Ÿ‘ˆ Whether to suppress notifications for this request. + // This is useful for requests that are made in the background. + + reattempts: 3, // ๐Ÿ‘ˆ The number of times to retry the request if it fails. + // Reattempts are only made if the request fails due to a network error. Responses with + // status codes in the 400s and 500s are not retried. + + onResponse: (res) => { // ๐Ÿ‘ˆ An optional callback that is called after the response is received. + if (res.status === 403) { + navigate("/forbidden"); + } + }, + // This is useful for handling responses with status codes in the 400s and 500s for a specific request. +}; +``` + +#### `ReturnType` + +The `useQuery` hook returns an object with the following properties: + +```ts +{ + res: Res | undefined; // ๐Ÿ‘ˆ The response object. `undefined` if the request has not been made yet. + + data: TRes | null; // ๐Ÿ‘ˆ The response body. `null` if the request has not been made yet. + + error: any; // ๐Ÿ‘ˆ The error that occurred while making the request if any. + + loading: boolean; // ๐Ÿ‘ˆ Whether the request is currently in progress. + + refetch: () => void; // ๐Ÿ‘ˆ A function that can be called to refetch the data. + // Ideal for revalidating stale data after a mutation. +} +``` + +## `request` + +`request` is a function that allows you to fetch data. It is a wrapper around `fetch` that adds some useful features. It can be used in both React components and non-React code. For fetching data in React components, prefer using `useQuery`. For mutations, use `request`. + +### `request` usage + +```ts +import { request } from "@care/request"; +import FooRoutes from "@foo/routes"; + +export default async function updateFoo(id: string, object: Foo) { + const { res, data } = await request(FooRoutes.updateFoo, { + pathParams: { id }, + body: object, // ๐Ÿ‘ˆ The body is automatically serialized to JSON. + }); + + if (res.status === 403) { + navigate("/forbidden"); + return null; + } + + return data; +} +``` From 5009a86abfb96e9b21f8371635176e4c3f73c5a7 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 21 Sep 2023 11:24:21 +0530 Subject: [PATCH 12/25] Prescriptions: Shrink discontinued prescriptions + Flip MAR timeline + Freeze primary columns in horizontal scroll (#6282) * MAR Table: reverse adm. intervals (#6177) * MAR Table: freeze columns (fixes #6177) * MAR Table: shrink discontinued medicines (#6151) * MAR Table: fix column freeze * fix discontinued button alignment * fix reversed slots and improper snapping * fix mobile responsiveness --- src/Common/hooks/useRangePagination.ts | 32 ++- .../PrescriptionAdministrationsTable.tsx | 266 +++++++++++------- src/Redux/actions.tsx | 2 +- 3 files changed, 180 insertions(+), 120 deletions(-) diff --git a/src/Common/hooks/useRangePagination.ts b/src/Common/hooks/useRangePagination.ts index 7652ae546c1..e6bbe9f573e 100644 --- a/src/Common/hooks/useRangePagination.ts +++ b/src/Common/hooks/useRangePagination.ts @@ -9,17 +9,18 @@ interface Props { bounds: DateRange; perPage: number; slots?: number; - defaultEnd?: boolean; + snapToLatest?: boolean; + reverse?: boolean; } const useRangePagination = ({ bounds, perPage, ...props }: Props) => { const [currentRange, setCurrentRange] = useState( - getInitialBounds(bounds, perPage, props.defaultEnd) + getInitialBounds(bounds, perPage, props.snapToLatest) ); useEffect(() => { - setCurrentRange(getInitialBounds(bounds, perPage, props.defaultEnd)); - }, [bounds, perPage, props.defaultEnd]); + setCurrentRange(getInitialBounds(bounds, perPage, props.snapToLatest)); + }, [bounds, perPage, props.snapToLatest]); const next = () => { const { end } = currentRange; @@ -62,17 +63,24 @@ const useRangePagination = ({ bounds, perPage, ...props }: Props) => { } const slots: DateRange[] = []; - const { start } = currentRange; + const { start, end } = currentRange; const delta = perPage / props.slots; for (let i = 0; i < props.slots; i++) { - slots.push({ - start: new Date(start.valueOf() + delta * i), - end: new Date(start.valueOf() + delta * (i + 1)), - }); + if (props.snapToLatest) { + slots.push({ + start: new Date(end.valueOf() - delta * (i - 1)), + end: new Date(end.valueOf() - delta * i), + }); + } else { + slots.push({ + start: new Date(start.valueOf() + delta * i), + end: new Date(start.valueOf() + delta * (i + 1)), + }); + } } - return slots; + return props.reverse ? slots.reverse() : slots; }, [currentRange, props.slots, perPage]); return { @@ -90,7 +98,7 @@ export default useRangePagination; const getInitialBounds = ( bounds: DateRange, perPage: number, - defaultEnd?: boolean + snapToLatest?: boolean ) => { const deltaBounds = bounds.end.valueOf() - bounds.start.valueOf(); @@ -98,7 +106,7 @@ const getInitialBounds = ( return bounds; } - if (defaultEnd) { + if (snapToLatest) { return { start: new Date(bounds.end.valueOf() - perPage), end: bounds.end, diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 81282126d7c..470caa1042b 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -47,6 +47,10 @@ export default function PrescriptionAdministrationsTable({ const { t } = useTranslation(); const [state, setState] = useState(); + + const [showDiscontinued, setShowDiscontinued] = useState(false); + const [discontinuedCount, setDiscontinuedCount] = useState(); + const pagination = useRangePagination({ bounds: state?.administrationsTimeBounds ?? { start: new Date(), @@ -54,7 +58,8 @@ export default function PrescriptionAdministrationsTable({ }, perPage: 24 * 60 * 60 * 1000, slots: 24, - defaultEnd: true, + snapToLatest: true, + reverse: true, }); const [showBulkAdminister, setShowBulkAdminister] = useState(false); @@ -64,8 +69,13 @@ export default function PrescriptionAdministrationsTable({ ); const refetch = useCallback(async () => { + const filters = { + is_prn: prn, + prescription_type: "REGULAR", + }; + const res = await dispatch( - list({ is_prn: prn, prescription_type: "REGULAR" }) + list(showDiscontinued ? filters : { ...filters, discontinued: false }) ); setState({ @@ -74,7 +84,14 @@ export default function PrescriptionAdministrationsTable({ ), administrationsTimeBounds: getAdministrationBounds(res.data.results), }); - }, [consultation_id, dispatch]); + + if (showDiscontinued === false) { + const discontinuedRes = await dispatch( + list({ ...filters, discontinued: true, limit: 0 }) + ); + setDiscontinuedCount(discontinuedRes.data.count); + } + }, [consultation_id, showDiscontinued, dispatch]); useEffect(() => { refetch(); @@ -141,17 +158,22 @@ export default function PrescriptionAdministrationsTable({ } /> -
- +
+
- - - )) - : pagination.slots?.map(({ start, end }, index) => ( - - ))} + : pagination.slots + ?.map(({ start, end }, index) => ( + + )) + .reverse()}
{t("medicine")} -

Dosage &

-

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

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

Dosage &

+

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

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

-

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

-

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

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

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

+

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

+ + + Administration(s) between +
+ {formatTime(start)} and{" "} + {formatTime(end)} +
+ on {formatDate(start)} +
+
@@ -227,6 +255,23 @@ export default function PrescriptionAdministrationsTable({
+ {showDiscontinued === false && !!discontinuedCount && ( + setShowDiscontinued(true)} + > + + + + Show {discontinuedCount} other discontinued + prescription(s) + + + + )} + {state?.prescriptions.length === 0 && (
@@ -283,12 +328,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { }, [prescription.id, dispatch, props.intervals]); return ( - + <> {showDiscontinue && ( {
)} - setShowDetails(true)} + -
- - {prescription.medicine_object?.name ?? prescription.medicine_old} - + setShowDetails(true)} + > +
+
+ + {prescription.medicine_object?.name ?? + prescription.medicine_old} + - {prescription.discontinued && ( - - {t("discontinued")} - - )} + {prescription.discontinued && ( + + {t("discontinued")} + + )} - {prescription.route && ( - - {t(prescription.route)} - - )} -
- - - -

{prescription.dosage}

-

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

- - - - {/* Administration Cells */} - {props.intervals.map(({ start, end }, index) => ( - - {administrations === undefined ? ( - - ) : ( - - )} + {prescription.route && ( + + {t(prescription.route)} + + )} +
+ +
+

{prescription.dosage}

+

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

+
+
- ))} - - - {/* Action Buttons */} - - setShowAdminister(true)} - > - {t("administer")} - - - + + + {/* Administration Cells */} + {props.intervals + .map(({ start, end }, index) => ( + + {administrations === undefined ? ( + + ) : ( + + )} + + )) + .reverse()} + + + {/* Action Buttons */} + + setShowAdminister(true)} + > + {t("administer")} + + + + ); }; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 6e0d91fc59d..1a5bd7e4fb1 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1003,7 +1003,7 @@ export const PrescriptionActions = (consultation_external_id: string) => { const pathParams = { consultation_external_id }; return { - list: (query?: Partial) => { + list: (query?: Record) => { let altKey; if (query?.is_prn !== undefined) { altKey = query?.is_prn From f4bddb950bc0ce8b6af631373c56d34ce4d60acb Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:31:56 +0530 Subject: [PATCH 13/25] Fix action field in consultation form (#6314) --- src/Components/Facility/ConsultationForm.tsx | 111 +++++++++---------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index ca2301b5964..6c7e8a6760e 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -97,7 +97,7 @@ type FormDetails = { procedure: ProcedureType[]; investigation: InvestigationType[]; is_telemedicine: BooleanStrings; - action?: string; + action?: number; assigned_to: string; assigned_to_object: UserModel | null; special_instruction: string; @@ -143,7 +143,7 @@ const initForm: FormDetails = { procedure: [], investigation: [], is_telemedicine: "false", - action: "NO_ACTION", + action: 10, assigned_to: "", assigned_to_object: null, special_instruction: "", @@ -279,15 +279,14 @@ export const ConsultationForm = (props: any) => { setIsLoading(true); const res = await dispatchAction(getPatient({ id: patientId })); if (res.data) { - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); if (isUpdate) { - const form = { ...state.form }; - form.action = TELEMEDICINE_ACTIONS.find( - (a) => a.id === res.data.action - )?.text; - dispatch({ type: "set_form", form }); + dispatch({ + type: "set_form", + form: { ...state.form, action: res.data.action }, + }); } + setPatientName(res.data.name); + setFacilityName(res.data.facility_object.name); } } else { setPatientName(""); @@ -302,6 +301,49 @@ export const ConsultationForm = (props: any) => { !!state.form.symptoms.length && !state.form.symptoms.includes(1); const isOtherSymptomsSelected = state.form.symptoms.includes(9); + const handleFormFieldChange: FieldChangeEventHandler = (event) => { + if (event.name === "consultation_status" && event.value === "1") { + dispatch({ + type: "set_form", + form: { + ...state.form, + consultation_status: 1, + symptoms: [1], + symptoms_onset_date: new Date(), + category: "Critical", + suggestion: "DD", + }, + }); + } else if (event.name === "suggestion" && event.value === "DD") { + dispatch({ + type: "set_form", + form: { + ...state.form, + suggestion: "DD", + consultation_notes: "Patient declared dead", + verified_by: "Declared Dead", + }, + }); + } else if ( + event.name === "icd11_diagnoses_object" || + event.name === "icd11_provisional_diagnoses_object" + ) { + dispatch({ + type: "set_form", + form: { + ...state.form, + [event.name]: event.value, + icd11_principal_diagnosis: undefined, + }, + }); + } else { + dispatch({ + type: "set_form", + form: { ...state.form, [event.name]: event.value }, + }); + } + }; + const fetchData = useCallback( async (status: statusType) => { if (!patientId) setIsLoading(true); @@ -352,7 +394,7 @@ export const ConsultationForm = (props: any) => { death_confirmed_doctor: res.data?.death_confirmed_doctor || "", InvestigationAdvice: res.data.investigation, }; - dispatch({ type: "set_form", form: formData }); + dispatch({ type: "set_form", form: { ...state.form, ...formData } }); setBed(formData.bed); if (res.data.last_daily_round) { @@ -364,7 +406,7 @@ export const ConsultationForm = (props: any) => { setIsLoading(false); } }, - [dispatchAction, id] + [dispatchAction, id, patientName, patientId] ); useAbortableEffect( @@ -745,49 +787,6 @@ export const ConsultationForm = (props: any) => { } }; - const handleFormFieldChange: FieldChangeEventHandler = (event) => { - if (event.name === "consultation_status" && event.value === "1") { - dispatch({ - type: "set_form", - form: { - ...state.form, - consultation_status: 1, - symptoms: [1], - symptoms_onset_date: new Date(), - category: "Critical", - suggestion: "DD", - }, - }); - } else if (event.name === "suggestion" && event.value === "DD") { - dispatch({ - type: "set_form", - form: { - ...state.form, - suggestion: "DD", - consultation_notes: "Patient declared dead", - verified_by: "Declared Dead", - }, - }); - } else if ( - event.name === "icd11_diagnoses_object" || - event.name === "icd11_provisional_diagnoses_object" - ) { - dispatch({ - type: "set_form", - form: { - ...state.form, - [event.name]: event.value, - icd11_principal_diagnosis: undefined, - }, - }); - } else { - dispatch({ - type: "set_form", - form: { ...state.form, [event.name]: event.value }, - }); - } - }; - const handleDoctorSelect = (event: FieldChangeEvent) => { if (event.value?.id) { dispatch({ @@ -1347,12 +1346,12 @@ export const ConsultationForm = (props: any) => {
option.desc} - optionValue={(option) => option.text} + optionDescription={() => ""} />
From c0f0e4b0e4beaf2c7093667be80b7cd71efa8323 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:54:42 +0530 Subject: [PATCH 14/25] fix loader (#6204) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/Components/Patient/PatientNotes.tsx | 111 ++++++++++++------------ 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/src/Components/Patient/PatientNotes.tsx b/src/Components/Patient/PatientNotes.tsx index b7993ed1ea3..68a5ab4dc5d 100644 --- a/src/Components/Patient/PatientNotes.tsx +++ b/src/Components/Patient/PatientNotes.tsx @@ -101,10 +101,6 @@ const PatientNotes = (props: PatientNotesProps) => { }); }; - if (isLoading) { - return ; - } - return (
{

Add new notes