From fa9a2c1578dd59699318d66efab5f7eacbfd68b2 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 10 Jun 2026 10:32:39 +0530 Subject: [PATCH 1/6] ENG-551 Replace all other usages of GovtOrgSelector with the new picker --- src/components/Facility/FacilityForm.tsx | 37 ++-- src/components/Users/UserForm.tsx | 36 ++-- .../components/GovtOrganizationSelector.tsx | 196 ------------------ .../PatientRegistration.tsx | 24 ++- 4 files changed, 56 insertions(+), 237 deletions(-) delete mode 100644 src/pages/Organization/components/GovtOrganizationSelector.tsx diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index 65cd7264ba6..37a20e2111e 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -25,11 +25,11 @@ import { PhoneInput } from "@/components/ui/phone-input"; import { Textarea } from "@/components/ui/textarea"; import LocationPicker from "@/components/Common/GeoLocationPicker"; +import GovtOrganizationPicker from "@/components/Organization/GovtOrganizationPicker"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import validators from "@/Utils/validators"; -import GovtOrganizationSelector from "@/pages/Organization/components/GovtOrganizationSelector"; import { FACILITY_FEATURE_TYPES, FACILITY_TYPES, @@ -53,7 +53,9 @@ export default function FacilityForm({ const { t } = useTranslation(); const queryClient = useQueryClient(); const [isGettingLocation, setIsGettingLocation] = useState(false); - const [selectedLevels, setSelectedLevels] = useState([]); + const [selectedGeoOrg, setSelectedGeoOrg] = useState( + null, + ); const facilityFormSchema = z.object({ facility_type: z.string().min(1, t("facility_type_required")), @@ -101,9 +103,7 @@ export default function FacilityForm({ }); useEffect(() => { - const levels: Organization[] = []; - if (org && org.org_type === "govt") levels.push(org); - setSelectedLevels(levels); + setSelectedGeoOrg(org && org.org_type === "govt" ? org : null); }, [org, organizationId]); const { mutate: createFacility, isPending } = useMutation({ @@ -194,7 +194,7 @@ export default function FacilityForm({ // Update form when facility data is loaded useEffect(() => { if (facilityData) { - setSelectedLevels([facilityData.geo_organization]); + setSelectedGeoOrg(facilityData.geo_organization); form.reset({ facility_type: facilityData.facility_type, name: facilityData.name, @@ -354,20 +354,25 @@ export default function FacilityForm({ ( + render={({ field, fieldState }) => (
- - form.setValue("geo_organization", value, { - shouldDirty: true, - }) - } + { + setSelectedGeoOrg(organization); + const isValid = + !!organization && !organization.has_children; + form.setValue( + "geo_organization", + isValid ? organization.id : "", + { shouldDirty: true }, + ); + }} />
diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 45517ec7875..2a025fe73bf 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -45,7 +45,7 @@ import { GENDERS, GENDER_TYPES, NAME_PREFIXES } from "@/common/constants"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import validators from "@/Utils/validators"; -import GovtOrganizationSelector from "@/pages/Organization/components/GovtOrganizationSelector"; +import GovtOrganizationPicker from "@/components/Organization/GovtOrganizationPicker"; import { Organization } from "@/types/organization/organization"; import organizationApi from "@/types/organization/organizationApi"; import { UserCreate, UserReadMinimal, UserUpdate } from "@/types/user/user"; @@ -70,9 +70,10 @@ export default function UserForm({ const { t } = useTranslation(); const isEditMode = !!existingUsername; const queryClient = useQueryClient(); - const [selectedLevels, setSelectedLevels] = useState([]); + const [selectedGeoOrg, setSelectedGeoOrg] = useState( + null, + ); const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); - const roleOrgSchema = z.object({ organization: z.string().min(1, t("select_role_organization")), role: z.string().min(1, t("please_select_role")), @@ -377,16 +378,12 @@ export default function UserForm({ }); useEffect(() => { - const levels: Organization[] = []; - if (org && org.org_type === "govt") levels.push(org); - setSelectedLevels(levels); + setSelectedGeoOrg(org && org.org_type === "govt" ? org : null); }, [org, organizationId]); useEffect(() => { - const levels: Organization[] = []; if (isEditMode && userData?.geo_organization) { - levels.push(userData.geo_organization); - setSelectedLevels(levels); + setSelectedGeoOrg(userData.geo_organization); } }, [userData, isEditMode]); @@ -798,19 +795,20 @@ export default function UserForm({ ( + render={({ field, fieldState }) => ( - - form.setValue("geo_organization", value, { - shouldDirty: true, - }) - } + { + setSelectedGeoOrg(organization); + form.setValue("geo_organization", organization?.id ?? "", { + shouldDirty: true, + }); + }} /> diff --git a/src/pages/Organization/components/GovtOrganizationSelector.tsx b/src/pages/Organization/components/GovtOrganizationSelector.tsx deleted file mode 100644 index 8874f187a53..00000000000 --- a/src/pages/Organization/components/GovtOrganizationSelector.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { Loader2 } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Autocomplete from "@/components/ui/autocomplete"; -import { Label } from "@/components/ui/label"; - -import { FilterState } from "@/hooks/useFilters"; -import { useGovtOrganizationLevel } from "@/hooks/useGovtOrganizationLevel"; - -import { Organization } from "@/types/organization/organization"; - -interface GovtOrganizationSelectorProps { - value?: string; - onChange: (value: string) => void; - required?: boolean; - requiredDepth?: number; - authToken?: string; - selected?: Organization[]; - - ref?: React.RefCallback; - - "aria-invalid"?: boolean; -} - -interface OrganizationLevelProps { - index: number; - currentLevel?: Organization; - previousLevel?: Organization; - onChange: ( - filter: FilterState, - index: number, - organization: Organization, - ) => void; - required?: boolean; - authToken?: string; - - isError?: boolean; - - ref?: React.RefCallback; -} - -function OrganizationLevelSelect({ - index, - currentLevel, - previousLevel, - onChange, - required, - authToken, - isError, - ref, -}: OrganizationLevelProps) { - const { t } = useTranslation(); - - const parentId = index === 0 ? "" : previousLevel?.id || ""; - - const { options, handleChange, handleSearch, organizations, isFetching } = - useGovtOrganizationLevel({ - index, - onChange: (filter: FilterState, index: number) => { - const selectedOrg = organizations?.find( - (org) => org.id === filter.organization, - ); - if (selectedOrg) { - onChange(filter, index, selectedOrg); - } - }, - parentId, - authToken, - }); - - return ( -
- -
- {isFetching && } - -
-
- ); -} - -/** - * @deprecated This component is deprecated and should not be used. - * Use `GovtOrganizationPicker` instead. This component is known for weird bugs. - */ -export default function GovtOrganizationSelector({ - onChange, - required, - selected, - authToken, - requiredDepth, - ...props -}: GovtOrganizationSelectorProps) { - const [selectedLevels, setSelectedLevels] = useState(selected || []); - - useEffect(() => { - // Needs the child-most level to be selected to be valid - if (required && selectedLevels[selectedLevels.length - 1]?.has_children) { - onChange(""); - return; - } - - if (requiredDepth != null && selectedLevels.length < requiredDepth) { - onChange(""); - return; - } - }, [selectedLevels]); - - useEffect(() => { - if (selected && selected.length > 0) { - let currentOrg = selected[0]; - if (currentOrg?.level_cache === 0) { - setSelectedLevels(selected); - } else { - const levels: Organization[] = []; - while (currentOrg && currentOrg.level_cache >= 0) { - levels.unshift(currentOrg); - currentOrg = currentOrg.parent as unknown as Organization; - } - setSelectedLevels(levels); - } - } - }, [selected]); - - const handleFilterChange = ( - filter: FilterState, - index: number, - organization: Organization, - ) => { - if (filter.organization) { - setSelectedLevels((prev) => { - const newLevels = prev.slice(0, index); - newLevels.push(organization); - return newLevels; - }); - if (!required || (required && !organization.has_children)) { - onChange(organization.id); - } else if ( - requiredDepth != null && - selectedLevels.length >= requiredDepth - ) { - onChange(organization.id); - } else { - onChange(""); - } - } else { - onChange(""); - // Reset subsequent levels when clearing a selection - setSelectedLevels((prev) => prev.slice(0, index)); - } - }; - - // Calculate the number of levels to show based on selectedLevels and has_children - const totalLevels = - selectedLevels.length + - (selectedLevels.length === 0 || - selectedLevels[selectedLevels.length - 1]?.has_children - ? 1 - : 0); - - return ( - <> - {Array.from({ length: totalLevels }).map((_, index) => ( - - ))} - - ); -} diff --git a/src/pages/PublicAppointments/PatientRegistration.tsx b/src/pages/PublicAppointments/PatientRegistration.tsx index 900c249b9c5..33c63b7264c 100644 --- a/src/pages/PublicAppointments/PatientRegistration.tsx +++ b/src/pages/PublicAppointments/PatientRegistration.tsx @@ -1,6 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { navigate, useNavigationPrompt, useQueryParams } from "raviger"; +import { useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -20,6 +21,8 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; +import GovtOrganizationPicker from "@/components/Organization/GovtOrganizationPicker"; + import { usePatientContext } from "@/hooks/usePatientUser"; import { GENDERS, GENDER_TYPES } from "@/common/constants"; @@ -29,9 +32,9 @@ import { usePubSub } from "@/Utils/pubsubContext"; import mutate from "@/Utils/request/mutate"; import { dateQueryString } from "@/Utils/utils"; import validators from "@/Utils/validators"; -import GovtOrganizationSelector from "@/pages/Organization/components/GovtOrganizationSelector"; import { PublicPatientRead } from "@/types/emr/patient/patient"; import publicPatientApi from "@/types/emr/patient/publicPatientApi"; +import { Organization } from "@/types/organization/organization"; import PublicAppointmentApi from "@/types/scheduling/PublicAppointmentApi"; import { PublicAppointment } from "@/types/scheduling/schedule"; @@ -52,6 +55,10 @@ export function PatientRegistration(props: PatientRegistrationProps) { const patientUserContext = usePatientContext(); const tokenData = patientUserContext?.tokenData; + const [selectedGeoOrg, setSelectedGeoOrg] = useState( + null, + ); + const patientSchema = z .object({ name: z @@ -370,15 +377,20 @@ export function PatientRegistration(props: PatientRegistrationProps) { ( + render={({ field, fieldState }) => ( - { - field.onChange(value); + value={selectedGeoOrg} + onChange={(organization) => { + setSelectedGeoOrg(organization); + const isValid = + !!organization && !organization.has_children; + field.onChange(isValid ? organization.id : ""); }} /> From d2d0c32c9c2020503487848a0dc43bbcc2df63d8 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 10 Jun 2026 10:40:55 +0530 Subject: [PATCH 2/6] remove useGovtOrganizationLevel --- src/hooks/useGovtOrganizationLevel.ts | 79 --------------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/hooks/useGovtOrganizationLevel.ts diff --git a/src/hooks/useGovtOrganizationLevel.ts b/src/hooks/useGovtOrganizationLevel.ts deleted file mode 100644 index b05c60e445e..00000000000 --- a/src/hooks/useGovtOrganizationLevel.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; - -import { FilterState } from "@/hooks/useFilters"; - -import query from "@/Utils/request/query"; -import { Organization } from "@/types/organization/organization"; -import organizationApi from "@/types/organization/organizationApi"; - -interface UseGovtOrganizationLevelProps { - index: number; - onChange: (filter: FilterState, index: number) => void; - parentId: string; - authToken?: string; -} - -interface AutoCompleteOption { - label: string; - value: string; -} - -export function useGovtOrganizationLevel({ - index, - onChange, - parentId, - authToken, -}: UseGovtOrganizationLevelProps) { - const [searchQuery, setSearchQuery] = useState(""); - - const headers = authToken - ? { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } - : {}; - - const { data: organizations, isFetching } = useQuery({ - queryKey: ["organizations-level", parentId, searchQuery], - queryFn: query.debounced(organizationApi.list, { - queryParams: { - org_type: "govt", - parent: parentId, - name: searchQuery || undefined, - limit: 200, - }, - ...headers, - }), - }); - - const handleChange = (value: string) => { - const selectedOrg = organizations?.results?.find( - (org: Organization) => org.id === value, - ); - - if (selectedOrg) { - onChange({ organization: selectedOrg.id }, index); - } - setSearchQuery(""); - }; - - const handleSearch = (query: string) => { - setSearchQuery(query); - }; - - const options: AutoCompleteOption[] = - organizations?.results?.map((org: Organization) => ({ - label: org.name, - value: org.id, - })) || []; - - return { - options, - handleChange, - handleSearch, - organizations: organizations?.results, - isFetching, - }; -} From f7bd2e7529c3c783cdfc8bc68612165c876bc473 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 10 Jun 2026 10:47:27 +0530 Subject: [PATCH 3/6] review comments --- src/components/Facility/FacilityForm.tsx | 12 ++++++++++-- src/components/Users/UserForm.tsx | 10 +++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index 37a20e2111e..ebc3cb6a9d6 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -103,8 +103,16 @@ export default function FacilityForm({ }); useEffect(() => { - setSelectedGeoOrg(org && org.org_type === "govt" ? org : null); - }, [org, organizationId]); + if (!organizationId) { + return; + } + + const govtOrg = org && org.org_type === "govt" ? org : null; + const isValid = !!govtOrg && !govtOrg.has_children; + + setSelectedGeoOrg(govtOrg); + form.setValue("geo_organization", isValid ? govtOrg.id : ""); + }, [org, organizationId, form]); const { mutate: createFacility, isPending } = useMutation({ mutationFn: mutate(facilityApi.create), diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 2a025fe73bf..2c1a33ddc0b 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -805,9 +805,13 @@ export default function UserForm({ value={selectedGeoOrg} onChange={(organization) => { setSelectedGeoOrg(organization); - form.setValue("geo_organization", organization?.id ?? "", { - shouldDirty: true, - }); + const isValid = + !!organization && !organization.has_children; + form.setValue( + "geo_organization", + isValid ? organization.id : "", + { shouldDirty: true }, + ); }} /> From ca8e6ef70c920030c38fc9f6396a9c12f86ce079 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Mon, 15 Jun 2026 12:20:32 +0530 Subject: [PATCH 4/6] fix edit in facility form --- src/components/Facility/FacilityForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index ebc3cb6a9d6..9ac5685709b 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -103,7 +103,7 @@ export default function FacilityForm({ }); useEffect(() => { - if (!organizationId) { + if (!organizationId || facilityId) { return; } @@ -112,7 +112,7 @@ export default function FacilityForm({ setSelectedGeoOrg(govtOrg); form.setValue("geo_organization", isValid ? govtOrg.id : ""); - }, [org, organizationId, form]); + }, [org, organizationId, facilityId, form]); const { mutate: createFacility, isPending } = useMutation({ mutationFn: mutate(facilityApi.create), From 273ca0343bbbeb8d36e21fd72b8ad9c70f6cc4f6 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 16 Jun 2026 17:10:49 +0530 Subject: [PATCH 5/6] allow selecting any level in facility and user form --- src/components/Facility/FacilityForm.tsx | 9 +++------ src/components/Users/UserForm.tsx | 12 ++++-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index 9ac5685709b..a7930c28992 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -108,10 +108,9 @@ export default function FacilityForm({ } const govtOrg = org && org.org_type === "govt" ? org : null; - const isValid = !!govtOrg && !govtOrg.has_children; setSelectedGeoOrg(govtOrg); - form.setValue("geo_organization", isValid ? govtOrg.id : ""); + form.setValue("geo_organization", govtOrg?.id ?? ""); }, [org, organizationId, facilityId, form]); const { mutate: createFacility, isPending } = useMutation({ @@ -369,15 +368,13 @@ export default function FacilityForm({ { setSelectedGeoOrg(organization); - const isValid = - !!organization && !organization.has_children; form.setValue( "geo_organization", - isValid ? organization.id : "", + organization?.id ?? "", { shouldDirty: true }, ); }} diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 2c1a33ddc0b..ce24bf43d18 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -801,17 +801,13 @@ export default function UserForm({ { setSelectedGeoOrg(organization); - const isValid = - !!organization && !organization.has_children; - form.setValue( - "geo_organization", - isValid ? organization.id : "", - { shouldDirty: true }, - ); + form.setValue("geo_organization", organization?.id ?? "", { + shouldDirty: true, + }); }} /> From c1bf5471525da6e52256e5372f5aaec9b4fa0997 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Fri, 26 Jun 2026 13:13:44 +0530 Subject: [PATCH 6/6] update behaviour --- src/components/Facility/FacilityForm.tsx | 9 ++++++--- src/components/Users/UserForm.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index a7930c28992..0dfbd205ca0 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -108,9 +108,10 @@ export default function FacilityForm({ } const govtOrg = org && org.org_type === "govt" ? org : null; + const isValid = !!govtOrg && !govtOrg.has_children; setSelectedGeoOrg(govtOrg); - form.setValue("geo_organization", govtOrg?.id ?? ""); + form.setValue("geo_organization", isValid ? govtOrg?.id : ""); }, [org, organizationId, facilityId, form]); const { mutate: createFacility, isPending } = useMutation({ @@ -368,13 +369,15 @@ export default function FacilityForm({ { setSelectedGeoOrg(organization); + const isValid = + !!organization && !organization.has_children; form.setValue( "geo_organization", - organization?.id ?? "", + isValid ? organization.id : "", { shouldDirty: true }, ); }} diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index ce24bf43d18..2a025fe73bf 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -801,7 +801,7 @@ export default function UserForm({ { setSelectedGeoOrg(organization);