From 4dd0e9345d03f281d4e526519a5bd000a4496851 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Fri, 3 Jan 2025 20:49:16 +0530 Subject: [PATCH 01/11] Autofill the State & District --- .../Patient/PatientRegistration.tsx | 37 +++++++++++++++++++ .../components/OrganizationSelector.tsx | 15 ++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 3ec12d7ce11..b2876d733ec 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { navigate } from "raviger"; import { Fragment, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import CareIcon from "@/CAREUI/icons/CareIcon"; import SectionNavigator from "@/CAREUI/misc/SectionNavigator"; @@ -48,6 +49,7 @@ import { } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { PatientModel, validatePatient } from "@/types/emr/patient"; +import organizationApi from "@/types/organization/organizationApi"; import Autocomplete from "../ui/autocomplete"; import InputWithError from "../ui/input-with-error"; @@ -79,6 +81,7 @@ export default function PatientRegistration( const [suppressDuplicateWarning, setSuppressDuplicateWarning] = useState(!!patientId); const [debouncedNumber, setDebouncedNumber] = useState(); + const [selectedLevels, setSelectedLevels] = useState([]); const sidebarItems = [ { label: t("patient__general-info"), id: "general-info" }, @@ -201,6 +204,21 @@ export default function PatientRegistration( const { statename: _stateName, districtname: _districtName } = pincodeDetails; + const stateOrg = await fetchOrganizationByName(_stateName); + if (!stateOrg) { + setSelectedLevels([]); + return; + } + + console.log("stateOrg", stateOrg); + + const districtOrg = await fetchOrganizationByName( + _districtName, + stateOrg.id, + ); + + setSelectedLevels([stateOrg, districtOrg]); + setShowAutoFilledPincode(true); setTimeout(() => { setShowAutoFilledPincode(false); @@ -304,6 +322,24 @@ export default function PatientRegistration( return ; } + async function fetchOrganizationByName(name: string, parentId?: string) { + try { + const data = await query(organizationApi.list, { + queryParams: { + org_type: "govt", + parent: parentId || "", + name, + }, + })({ signal: new AbortController().signal }); + console.log("data", data.results); + return data.results?.[0]; + } catch (error) { + console.error("Error fetching org:", error); + toast.error("Error fetching organization"); + return undefined; + } + } + return (
@@ -637,6 +673,7 @@ export default function PatientRegistration( <> setForm((f) => ({ ...f, diff --git a/src/pages/Organization/components/OrganizationSelector.tsx b/src/pages/Organization/components/OrganizationSelector.tsx index 14ea412cc65..7074e8e7952 100644 --- a/src/pages/Organization/components/OrganizationSelector.tsx +++ b/src/pages/Organization/components/OrganizationSelector.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -20,6 +20,7 @@ interface OrganizationSelectorProps { onChange: (value: string) => void; required?: boolean; authToken?: string; + parentSelectedLevels?: Organization[]; } interface AutoCompleteOption { @@ -28,8 +29,10 @@ interface AutoCompleteOption { } export default function OrganizationSelector(props: OrganizationSelectorProps) { - const { onChange, required } = props; - const [selectedLevels, setSelectedLevels] = useState([]); + const { onChange, required, parentSelectedLevels } = props; + const [selectedLevels, setSelectedLevels] = useState( + parentSelectedLevels || [], + ); const [searchQuery, setSearchQuery] = useDebouncedState("", 500); const headers = props.authToken @@ -108,6 +111,12 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { setSelectedLevels((prev) => prev.slice(0, level)); }; + useEffect(() => { + if (parentSelectedLevels) { + setSelectedLevels(parentSelectedLevels); + } + }, [parentSelectedLevels]); + return ( <> {/* Selected Levels */} From 9cb67dfe9d7e990109f4d2b8d101e64652a4c495 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 4 Jan 2025 00:50:49 +0530 Subject: [PATCH 02/11] Show asterisk properly and pass error message --- src/components/Patient/PatientRegistration.tsx | 1 + src/pages/Organization/components/OrganizationSelector.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index edc77373e2d..189d1c4433e 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -680,6 +680,7 @@ export default function PatientRegistration( geo_organization: value, })) } + errorMessage={errors.geo_organization?.[0]} /> )} diff --git a/src/pages/Organization/components/OrganizationSelector.tsx b/src/pages/Organization/components/OrganizationSelector.tsx index 5bdd5b045e9..76b7a19a665 100644 --- a/src/pages/Organization/components/OrganizationSelector.tsx +++ b/src/pages/Organization/components/OrganizationSelector.tsx @@ -21,6 +21,7 @@ interface OrganizationSelectorProps { required?: boolean; authToken?: string; parentSelectedLevels?: Organization[]; + errorMessage?: string; } interface AutoCompleteOption { @@ -154,7 +155,8 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) {
Date: Sat, 4 Jan 2025 01:51:51 +0530 Subject: [PATCH 03/11] Remove console logs --- src/components/Patient/PatientRegistration.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 189d1c4433e..b6d12447bd2 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -210,8 +210,6 @@ export default function PatientRegistration( return; } - console.log("stateOrg", stateOrg); - const districtOrg = await fetchOrganizationByName( _districtName, stateOrg.id, @@ -331,10 +329,8 @@ export default function PatientRegistration( name, }, })({ signal: new AbortController().signal }); - console.log("data", data.results); return data.results?.[0]; } catch (error) { - console.error("Error fetching org:", error); toast.error("Error fetching organization"); return undefined; } From 67073f81763d5ded3f30c40fed85337cd7950cbd Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 4 Jan 2025 02:37:22 +0530 Subject: [PATCH 04/11] Inform parent about deletion & handle it --- .../components/OrganizationSelector.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pages/Organization/components/OrganizationSelector.tsx b/src/pages/Organization/components/OrganizationSelector.tsx index 76b7a19a665..7f0c0cdb863 100644 --- a/src/pages/Organization/components/OrganizationSelector.tsx +++ b/src/pages/Organization/components/OrganizationSelector.tsx @@ -109,7 +109,19 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { }; const handleEdit = (level: number) => { - setSelectedLevels((prev) => prev.slice(0, level)); + const newLevels = selectedLevels.slice(0, level); + setSelectedLevels(newLevels); + + if (!newLevels.length) { + onChange(""); + } else { + const lastOrg = newLevels[newLevels.length - 1]; + if (!lastOrg.has_children) { + onChange(lastOrg.id); + } else { + onChange(""); + } + } }; useEffect(() => { From 5ba881ec5b3e56ed9919bd961dcae72c3830990e Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 4 Jan 2025 02:42:07 +0530 Subject: [PATCH 05/11] Add cursor style to address input when disabled --- src/components/Patient/PatientRegistration.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index b6d12447bd2..d7b34215e36 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -619,6 +619,7 @@ export default function PatientRegistration( setForm((f) => ({ ...f, permanent_address: e.target.value })) } disabled={sameAddress} + className={sameAddress ? "cursor-not-allowed" : ""} /> {/*
From 3b7713387dc04e54cf8a6decafa12efad48ff9bb Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 4 Jan 2025 03:08:22 +0530 Subject: [PATCH 06/11] Use organization type instead of any --- src/components/Patient/PatientRegistration.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index d7b34215e36..f95e3bf8430 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -48,6 +48,7 @@ import { } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { PatientModel, validatePatient } from "@/types/emr/patient"; +import { Organization } from "@/types/organization/organization"; import organizationApi from "@/types/organization/organizationApi"; import Autocomplete from "../ui/autocomplete"; @@ -81,7 +82,7 @@ export default function PatientRegistration( const [suppressDuplicateWarning, setSuppressDuplicateWarning] = useState(!!patientId); const [debouncedNumber, setDebouncedNumber] = useState(); - const [selectedLevels, setSelectedLevels] = useState([]); + const [selectedLevels, setSelectedLevels] = useState([]); const sidebarItems = [ { label: t("patient__general-info"), id: "general-info" }, @@ -215,7 +216,9 @@ export default function PatientRegistration( stateOrg.id, ); - setSelectedLevels([stateOrg, districtOrg]); + if (stateOrg && districtOrg) { + setSelectedLevels([stateOrg, districtOrg]); + } setShowAutoFilledPincode(true); setTimeout(() => { From bdd739f85d33c1c948bb4eb9e8583443c8f49835 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 6 Jan 2025 15:15:13 +0530 Subject: [PATCH 07/11] Add custom hook for organization data fetching --- src/hooks/useOrganization.ts | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/hooks/useOrganization.ts diff --git a/src/hooks/useOrganization.ts b/src/hooks/useOrganization.ts new file mode 100644 index 00000000000..0c4e2e81ee2 --- /dev/null +++ b/src/hooks/useOrganization.ts @@ -0,0 +1,38 @@ +import { useQuery } from "@tanstack/react-query"; + +import query from "@/Utils/request/query"; +import { Organization } from "@/types/organization/organization"; +import organizationApi from "@/types/organization/organizationApi"; + +interface UseOrganizationParams { + orgType?: string; + parentId?: string; + name?: string; + enabled?: boolean; +} + +export function useOrganization({ + orgType = "", + parentId = "", + name = "", + enabled = true, +}: UseOrganizationParams) { + const { data, isLoading, isError } = useQuery<{ results: Organization[] }>({ + queryKey: ["organization", orgType, name, parentId], + queryFn: query(organizationApi.list, { + queryParams: { + org_type: orgType, + parent: parentId, + name, + }, + }), + enabled: enabled && !!name, + select: (res) => ({ results: res.results }), + }); + + return { + organizations: data?.results || [], + isLoading, + isError, + }; +} From 6905db313c932aabafc279d554c5f90ccb07571a Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 6 Jan 2025 15:15:48 +0530 Subject: [PATCH 08/11] Add custom hook to fetch state and district from pincode --- src/hooks/useStateAndDistrictFromPincode.ts | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/hooks/useStateAndDistrictFromPincode.ts diff --git a/src/hooks/useStateAndDistrictFromPincode.ts b/src/hooks/useStateAndDistrictFromPincode.ts new file mode 100644 index 00000000000..38f2b1c1ea8 --- /dev/null +++ b/src/hooks/useStateAndDistrictFromPincode.ts @@ -0,0 +1,62 @@ +import careConfig from "@careConfig"; +import { useQuery } from "@tanstack/react-query"; + +import { validatePincode } from "@/common/validation"; + +import { getPincodeDetails } from "@/Utils/utils"; + +import { useOrganization } from "./useOrganization"; + +interface UseStateAndDistrictProps { + pincode: string; +} + +export function useStateAndDistrictFromPincode({ + pincode, +}: UseStateAndDistrictProps) { + const { + data: pincodeDetails, + isLoading: isPincodeLoading, + isError: isPincodeError, + } = useQuery({ + queryKey: ["pincode-details", pincode], + queryFn: () => getPincodeDetails(pincode, careConfig.govDataApiKey), + enabled: validatePincode(pincode), + }); + + const stateName = pincodeDetails?.statename || ""; + const districtName = pincodeDetails?.districtname || ""; + + const { + organizations: stateOrgs, + isLoading: isStateLoading, + isError: isStateError, + } = useOrganization({ + orgType: "govt", + parentId: "", + name: stateName, + enabled: !!stateName, + }); + + const stateOrg = stateOrgs?.[0]; + + const { + organizations: districtOrgs, + isLoading: isDistrictLoading, + isError: isDistrictError, + } = useOrganization({ + orgType: "govt", + parentId: stateOrg?.id, + name: districtName, + enabled: !!stateOrg?.id && !!districtName, + }); + + const districtOrg = districtOrgs[0]; + + return { + stateOrg, + districtOrg, + isLoading: isPincodeLoading || isStateLoading || isDistrictLoading, + isError: isPincodeError || isStateError || isDistrictError, + }; +} From 45a3d9ff09b91a23e539e59ff7572e42511b9d9d Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 6 Jan 2025 15:20:10 +0530 Subject: [PATCH 09/11] Refactor pincode handling to use custom hook for state and district retrieval --- .../Patient/PatientRegistration.tsx | 87 +++++-------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index f95e3bf8430..e2efc5a933c 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -1,10 +1,9 @@ -import careConfig from "@careConfig"; import { useMutation, useQuery } from "@tanstack/react-query"; import { navigate, useQueryParams } from "raviger"; import { Fragment, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; +import CareIcon from "@/CAREUI/icons/CareIcon"; import SectionNavigator from "@/CAREUI/misc/SectionNavigator"; import { Button } from "@/components/ui/button"; @@ -28,6 +27,7 @@ import Page from "@/components/Common/Page"; import DuplicatePatientDialog from "@/components/Facility/DuplicatePatientDialog"; import useAppHistory from "@/hooks/useAppHistory"; +import { useStateAndDistrictFromPincode } from "@/hooks/useStateAndDistrictFromPincode"; import { BLOOD_GROUP_CHOICES, // DOMESTIC_HEALTHCARE_SUPPORT_CHOICES, @@ -35,21 +35,15 @@ import { //RATION_CARD_CATEGORY, // SOCIOECONOMIC_STATUS_CHOICES , } from "@/common/constants"; import countryList from "@/common/static/countries.json"; -import { validatePincode } from "@/common/validation"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; -import { - dateQueryString, - getPincodeDetails, - parsePhoneNumber, -} from "@/Utils/utils"; +import { dateQueryString, parsePhoneNumber } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { PatientModel, validatePatient } from "@/types/emr/patient"; import { Organization } from "@/types/organization/organization"; -import organizationApi from "@/types/organization/organizationApi"; import Autocomplete from "../ui/autocomplete"; import InputWithError from "../ui/input-with-error"; @@ -192,47 +186,24 @@ export default function PatientRegistration( } }, [patientQuery.data]); - const handlePincodeChange = async (value: string) => { - if (!validatePincode(value)) return; - if (form.state && form.district) return; - - const pincodeDetails = await getPincodeDetails( - value, - careConfig.govDataApiKey, - ); - if (!pincodeDetails) return; - - const { statename: _stateName, districtname: _districtName } = - pincodeDetails; - - const stateOrg = await fetchOrganizationByName(_stateName); - if (!stateOrg) { - setSelectedLevels([]); - return; - } - - const districtOrg = await fetchOrganizationByName( - _districtName, - stateOrg.id, - ); - - if (stateOrg && districtOrg) { - setSelectedLevels([stateOrg, districtOrg]); - } - - setShowAutoFilledPincode(true); - setTimeout(() => { - setShowAutoFilledPincode(false); - }, 2000); - }; + const { stateOrg, districtOrg } = useStateAndDistrictFromPincode({ + pincode: form.pincode?.toString() || "", + }); useEffect(() => { - const timeout = setTimeout( - () => handlePincodeChange(form.pincode?.toString() || ""), - 1000, - ); - return () => clearTimeout(timeout); - }, [form.pincode]); + const levels: Organization[] = []; + if (stateOrg) levels.push(stateOrg); + if (districtOrg) levels.push(districtOrg); + setSelectedLevels(levels); + + if (levels.length > 0) { + setShowAutoFilledPincode(true); + const timer = setTimeout(() => { + setShowAutoFilledPincode(false); + }, 2000); + return () => clearTimeout(timer); + } + }, [stateOrg, districtOrg]); const title = !patientId ? t("add_details_of_patient") @@ -323,22 +294,6 @@ export default function PatientRegistration( return ; } - async function fetchOrganizationByName(name: string, parentId?: string) { - try { - const data = await query(organizationApi.list, { - queryParams: { - org_type: "govt", - parent: parentId || "", - name, - }, - })({ signal: new AbortController().signal }); - return data.results?.[0]; - } catch (error) { - toast.error("Error fetching organization"); - return undefined; - } - } - return (
@@ -638,7 +593,7 @@ export default function PatientRegistration( > - {/* {showAutoFilledPincode && ( + {_showAutoFilledPincode && (
- )} */} + )}
From 0ca6b73b4085ecfb69cd6cacd36df59fca878ba4 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 6 Jan 2025 17:22:19 +0530 Subject: [PATCH 10/11] Add error handling messages for organization and pincode fetching --- public/locale/en.json | 3 +++ src/hooks/useOrganization.ts | 6 ++++++ src/hooks/useStateAndDistrictFromPincode.ts | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/public/locale/en.json b/public/locale/en.json index 53586b56e86..f4c77636522 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1302,6 +1302,7 @@ "organization_forbidden": "You don't have access to any organizations yet.", "organization_not_found": "No Organizations Found", "organizations": "Organizations", + "organizations_fetch_error": "Error while fetching organizations", "origin_facility": "Current facility", "other_details": "Other details", "otp_verification_error": "Failed to verify OTP. Please try again later.", @@ -1392,6 +1393,8 @@ "phone_number_verified": "Phone Number Verified", "pincode": "Pincode", "pincode_autofill": "State and District auto-filled from Pincode", + "pincode_district_auto_fill_error": "Failed to auto-fill district information", + "pincode_state_auto_fill_error": "Failed to auto-fill state and district information", "play": "Play", "play_audio": "Play Audio", "please_assign_bed_to_patient": "Please assign a bed to this patient", diff --git a/src/hooks/useOrganization.ts b/src/hooks/useOrganization.ts index 0c4e2e81ee2..1d1a267e0d9 100644 --- a/src/hooks/useOrganization.ts +++ b/src/hooks/useOrganization.ts @@ -1,4 +1,6 @@ import { useQuery } from "@tanstack/react-query"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import query from "@/Utils/request/query"; import { Organization } from "@/types/organization/organization"; @@ -30,6 +32,10 @@ export function useOrganization({ select: (res) => ({ results: res.results }), }); + const { t } = useTranslation(); + + isError && toast.error(t("organizations_fetch_error")); + return { organizations: data?.results || [], isLoading, diff --git a/src/hooks/useStateAndDistrictFromPincode.ts b/src/hooks/useStateAndDistrictFromPincode.ts index 38f2b1c1ea8..11d3ac2109f 100644 --- a/src/hooks/useStateAndDistrictFromPincode.ts +++ b/src/hooks/useStateAndDistrictFromPincode.ts @@ -1,5 +1,7 @@ import careConfig from "@careConfig"; import { useQuery } from "@tanstack/react-query"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { validatePincode } from "@/common/validation"; @@ -24,6 +26,8 @@ export function useStateAndDistrictFromPincode({ enabled: validatePincode(pincode), }); + const { t } = useTranslation(); + const stateName = pincodeDetails?.statename || ""; const districtName = pincodeDetails?.districtname || ""; @@ -51,6 +55,12 @@ export function useStateAndDistrictFromPincode({ enabled: !!stateOrg?.id && !!districtName, }); + isStateError && toast.info(t("pincode_state_auto_fill_error")); + + isDistrictError && + !isStateError && + toast.info(t("pincode_district_auto_fill_error")); + const districtOrg = districtOrgs[0]; return { From c6b1783d62a211894efd326fc1944202fb467e3d Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 6 Jan 2025 17:22:40 +0530 Subject: [PATCH 11/11] Update condition for showing auto-filled pincode in PatientRegistration component --- src/components/Patient/PatientRegistration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index e2efc5a933c..1fcc0f15475 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -196,7 +196,7 @@ export default function PatientRegistration( if (districtOrg) levels.push(districtOrg); setSelectedLevels(levels); - if (levels.length > 0) { + if (levels.length == 2) { setShowAutoFilledPincode(true); const timer = setTimeout(() => { setShowAutoFilledPincode(false);