diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index 65cd7264ba6..a7930c28992 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,10 +103,15 @@ export default function FacilityForm({ }); useEffect(() => { - const levels: Organization[] = []; - if (org && org.org_type === "govt") levels.push(org); - setSelectedLevels(levels); - }, [org, organizationId]); + if (!organizationId || facilityId) { + return; + } + + const govtOrg = org && org.org_type === "govt" ? org : null; + + setSelectedGeoOrg(govtOrg); + form.setValue("geo_organization", govtOrg?.id ?? ""); + }, [org, organizationId, facilityId, form]); const { mutate: createFacility, isPending } = useMutation({ mutationFn: mutate(facilityApi.create), @@ -194,7 +201,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 +361,23 @@ export default function FacilityForm({ ( + render={({ field, fieldState }) => (
- - form.setValue("geo_organization", value, { - shouldDirty: true, - }) - } - required + { + setSelectedGeoOrg(organization); + form.setValue( + "geo_organization", + organization?.id ?? "", + { shouldDirty: true }, + ); + }} />
diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 45517ec7875..ce24bf43d18 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, { + { + setSelectedGeoOrg(organization); + form.setValue("geo_organization", organization?.id ?? "", { shouldDirty: true, - }) - } - required={false} + }); + }} /> 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, - }; -} 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 : ""); }} />