Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public Facilities Filters #9748

Merged
merged 22 commits into from
Jan 14, 2025
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
faa4abe
WIP: organization filter
Jacobjeevan Jan 2, 2025
099e91e
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 4, 2025
3abdfd9
Organization Filter
Jacobjeevan Jan 4, 2025
2349c84
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 5, 2025
db940f2
minor fixes
Jacobjeevan Jan 5, 2025
bc0560a
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 6, 2025
a57dfa6
Tweak logic to work with levels from BE than hardcoded
Jacobjeevan Jan 6, 2025
c4ca814
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 6, 2025
be5bcaa
Update organisation filter and search bar styling on public facilitie…
vinutv Jan 7, 2025
2b62571
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 8, 2025
5ff2f0f
added query for root level, styling changes
Jacobjeevan Jan 8, 2025
31986af
moved logic to sep file&hook as per cursor suggestion
Jacobjeevan Jan 8, 2025
5537247
coderabbit suggestions
Jacobjeevan Jan 8, 2025
3ca311d
additional styling changes
Jacobjeevan Jan 8, 2025
def05a4
mobile responsiveness edits
Jacobjeevan Jan 8, 2025
f1f9b55
Merge branch 'develop' into organizationFilter
nihal467 Jan 9, 2025
04b8c10
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 11, 2025
06dba7b
switch to debounced query
Jacobjeevan Jan 11, 2025
cfd445c
styling and fixes
Jacobjeevan Jan 11, 2025
c3eb888
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 11, 2025
e0f4527
Merge branch 'develop' into organizationFilter
Jacobjeevan Jan 13, 2025
03ee501
disable facility type filter if location is not selected
Jacobjeevan Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Tweak logic to work with levels from BE than hardcoded
  • Loading branch information
Jacobjeevan committed Jan 6, 2025
commit a57dfa6cf0759d459149abcae6781ee1bdfd7c9c
6 changes: 3 additions & 3 deletions public/locale/en.json
Original file line number Diff line number Diff line change
@@ -1345,6 +1345,7 @@
"patient_consultation__treatment__summary__spo2": "SpO2",
"patient_consultation__treatment__summary__temperature": "Temperature",
"patient_created": "Patient Created",
"patient_dashboard": "Patient Dashboard",
"patient_details": "Patient Details",
"patient_details_incomplete": "Patient Details Incomplete",
"patient_face": "Patient Face",
@@ -1604,7 +1605,6 @@
"select": "Select",
"select_all": "Select All",
"select_date": "Select date",
"select_district": "Select District",
"select_eligible_policy": "Select an Eligible Insurance Policy",
"select_facility_for_discharged_patients_warning": "Facility needs to be selected to view discharged patients.",
"select_facility_type": "Select Facility Type",
@@ -1614,18 +1614,17 @@
"select_investigation_groups": "Select Investigation Groups",
"select_investigations": "Select Investigations",
"select_local_body": "Select Local Body",
"select_localbody": "Select Local Body",
"select_patient": "Select Patient",
"select_policy": "Select an Insurance Policy",
"select_policy_to_add_items": "Select a Policy to Add Items",
"select_practitioner": "Select Practicioner",
"select_previous": "Select previous fields",
"select_register_patient": "Select/Register Patient",
"select_role": "Select Role",
"select_seven_day_period": "Select a seven day period",
"select_skills": "Select and add some skills",
"select_time": "Select time",
"select_time_slot": "Select time slot",
"select_ward": "Select Ward",
"select_wards": "Select wards",
"self_booked": "Self-booked",
"send": "Send",
@@ -1665,6 +1664,7 @@
"show_default_presets": "Show Default Presets",
"show_patient_presets": "Show Patient Presets",
"show_unread_notifications": "Show Unread",
"sign_in": "Sign In",
"sign_out": "Sign Out",
"skill_add_error": "Error while adding skill",
"skill_added_successfully": "Skill added successfully",
3 changes: 2 additions & 1 deletion src/pages/Facility/FacilitiesPage.tsx
Original file line number Diff line number Diff line change
@@ -73,6 +73,7 @@ export function FacilitiesPage() {
const GetLoginHeader = () => {
if (
tokenData &&
tokenData.createdAt &&
dayjs(tokenData.createdAt).isAfter(dayjs().subtract(14, "minutes"))
) {
return (
@@ -141,7 +142,7 @@ export function FacilitiesPage() {
id="facility-search"
options={[
{
key: "facility_search_text",
key: "facility_search_placeholder_text",
type: "text" as const,
placeholder: t("facility_search_placeholder_text"),
value: qParams.search || "",
249 changes: 164 additions & 85 deletions src/pages/Organization/components/OrganizationFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQueries, useQuery } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";

import Autocomplete from "@/components/ui/autocomplete";
@@ -21,9 +21,6 @@ import query from "@/Utils/request/query";
import { Organization } from "@/types/organization/organization";
import organizationApi from "@/types/organization/organizationApi";

// TODO: fetch from the backend instead
const govtOrgLevels = ["District", "LocalBody", "Ward"];

interface OrganizationFilterProps {
selected: string[];
onChange: (Filter: FilterState, index?: number) => void;
@@ -37,11 +34,14 @@ interface AutoCompleteOption {
value: string;
}

const DEFAULT_ORG_LEVELS = 3;

export default function OrganizationFilter(props: OrganizationFilterProps) {
const { t } = useTranslation();
const { onChange, selected, skipLevels } = props;

const [selectedLevels, setSelectedLevels] = useState<Organization[]>([]);
const [orgTypes, setOrgTypes] = useState<string[]>([]);
const [selectedFacilityType, setSelectedFacilityType] = useState<
OptionsType | undefined
>(undefined);
@@ -69,6 +69,17 @@ export default function OrganizationFilter(props: OrganizationFilterProps) {

if (validOrgs.length > 0) {
setSelectedLevels(validOrgs);
const validOrg = validOrgs[0];
if (
validOrg &&
validOrg.metadata?.govt_org_type &&
validOrg.metadata?.govt_org_children_type
) {
setOrgTypes([
validOrg.metadata?.govt_org_type,
validOrg.metadata?.govt_org_children_type,
]);
}
} else {
setSelectedLevels([]);
}
@@ -93,96 +104,164 @@ export default function OrganizationFilter(props: OrganizationFilterProps) {

const clearSelections = () => {
setSelectedFacilityType(undefined);
setOrgTypes((prevTypes) => {
return [prevTypes[0], prevTypes[1]];
});
setSelectedLevels([]);
setTimeout(() => {
onChange({ organization: undefined, facility_type: undefined }, 0);
}, 0);
};

const RenderOrganizationLevel = (
level: Organization | undefined,
index: number,
) => {
const skip = skipLevels?.includes(index) || false;
const [levelSearch, setLevelSearch] = useDebouncedState("", 500);
const { data: availableOrgs } = useQuery<{ results: Organization[] }>({
queryKey: ["organizations-available", getParentId(index), levelSearch],
queryFn: query(organizationApi.getPublicOrganizations, {
queryParams: {
...(index > 0 && { parent: getParentId(index) }),
...(index === 0 && { level_cache: 1 }),
name: levelSearch || undefined,
},
}),
enabled:
!skip &&
index <= selectedLevels.length &&
(index === 0 || selectedLevels[index - 1] !== undefined),
});
if (skip) return null;
const options = getOrganizationOptions(availableOrgs?.results || []);

return (
<Autocomplete
key={`dropdown-${index}`}
popoverClassName="sm:min-w-56 sm:max-w-60 w-[calc(100vw-2rem)]"
value={selectedLevels[index]?.id || ""}
options={options}
onChange={(value: string) => {
const selectedOrg = availableOrgs?.results.find(
(org) => org.id === value,
);

if (selectedOrg) {
onChange({ organization: selectedOrg.id }, index);
setLevelSearch("");
return (
<div className="gap-3 flex flex-col sm:flex-col">
<div className="flex flex-row gap-3">
<Select
value={selectedFacilityType?.text || ""}
onValueChange={(value) => {
setSelectedFacilityType(
FACILITY_TYPES.find((type) => type.text === value),
);
onChange({
facility_type: FACILITY_TYPES.find((type) => type.text === value)
?.id,
});
}}
>
<SelectTrigger className="overflow-hidden sm:min-w-48 sm:max-w-56 w-[calc(100vw-1rem)]">
<SelectValue placeholder={t("select_facility_type")} />
</SelectTrigger>
<SelectContent>
{FACILITY_TYPES.map((type) => (
<SelectItem key={type.id} value={type.text}>
{type.text}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
onClick={clearSelections}
variant="white"
disabled={
selectedLevels.length === 0 && selectedFacilityType === undefined
}
}}
onSearch={(value) => setLevelSearch(value)}
placeholder={t(`select_${govtOrgLevels[index].toLowerCase()}`)}
disabled={index > selectedLevels.length}
align="start"
/>
);
};
>
{t("clear")}
</Button>
</div>
<div className="flex flex-row gap-3">
{[...Array(Math.min(orgTypes.length + 1, DEFAULT_ORG_LEVELS))].map(
(_, index) => (
<OrganizationLevel
key={`organization-level-${index}`}
index={index}
skip={skipLevels?.includes(index) || false}
selectedLevels={selectedLevels}
orgTypes={orgTypes}
setOrgTypes={setOrgTypes}
onChange={onChange}
getParentId={getParentId}
getOrganizationOptions={getOrganizationOptions}
/>
),
)}
</div>
</div>
);
}

interface OrganizationLevelProps {
index: number;
skip: boolean;
selectedLevels: Organization[];
orgTypes: string[];
setOrgTypes: React.Dispatch<React.SetStateAction<string[]>>;
onChange: (Filter: FilterState, index?: number) => void;
getParentId: (index: number) => string;
getOrganizationOptions: (orgs?: Organization[]) => AutoCompleteOption[];
}

function OrganizationLevel({
index,
skip,
selectedLevels,
orgTypes,
setOrgTypes,
onChange,
getParentId,
getOrganizationOptions,
}: OrganizationLevelProps) {
const [levelSearch, setLevelSearch] = useDebouncedState("", 500);
const { t } = useTranslation();
const { data: availableOrgs } = useQuery<{ results: Organization[] }>({
queryKey: ["organizations-available", getParentId(index), levelSearch],
queryFn: query(organizationApi.getPublicOrganizations, {
queryParams: {
...(index > 0 && { parent: getParentId(index) }),
...(index === 0 && { level_cache: 1 }),
name: levelSearch || undefined,
},
}),
enabled:
!skip &&
index <= selectedLevels.length &&
(index === 0 || selectedLevels[index - 1] !== undefined),
});
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for organization queries.

The query implementation lacks error handling, which could lead to a poor user experience if the API request fails.

   const { data: availableOrgs } = useQuery<{ results: Organization[] }>({
     queryKey: ["organizations-available", getParentId(index), levelSearch],
     queryFn: query(organizationApi.getPublicOrganizations, {
       queryParams: {
         ...(index > 0 && { parent: getParentId(index) }),
         ...(index === 0 && { level_cache: 1 }),
         name: levelSearch || undefined,
       },
     }),
+    onError: (error) => {
+      console.error('Failed to fetch organizations:', error);
+      // Consider showing a user-friendly error message
+    },
     enabled:
       !skip &&
       index <= selectedLevels.length &&
       (index === 0 || selectedLevels[index - 1] !== undefined),
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: availableOrgs } = useQuery<{ results: Organization[] }>({
queryKey: ["organizations-available", getParentId(index), levelSearch],
queryFn: query(organizationApi.getPublicOrganizations, {
queryParams: {
...(index > 0 && { parent: getParentId(index) }),
...(index === 0 && { level_cache: 1 }),
name: levelSearch || undefined,
},
}),
enabled:
!skip &&
index <= selectedLevels.length &&
(index === 0 || selectedLevels[index - 1] !== undefined),
});
const { data: availableOrgs } = useQuery<{ results: Organization[] }>({
queryKey: ["organizations-available", getParentId(index), levelSearch],
queryFn: query(organizationApi.getPublicOrganizations, {
queryParams: {
...(index > 0 && { parent: getParentId(index) }),
...(index === 0 && { level_cache: 1 }),
name: levelSearch || undefined,
},
}),
onError: (error) => {
console.error('Failed to fetch organizations:', error);
// Consider showing a user-friendly error message
},
enabled:
!skip &&
index <= selectedLevels.length &&
(index === 0 || selectedLevels[index - 1] !== undefined),
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global error handling will take care of it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jacobjeevan Understood, I'll retract the error handling suggestion.


✏️ Learnings added
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9748
File: src/pages/Organization/components/OrganizationFilter.tsx:0-0
Timestamp: 2025-01-08T07:49:59.376Z
Learning: The project implements global error handling for React Query errors, making local error handling in individual query hooks unnecessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


// Update org types when we get new data for this level
useEffect(() => {
if (selectedLevels[index]) {
const currentOrg = selectedLevels[index];
if (currentOrg?.metadata?.govt_org_children_type) {
setOrgTypes((prevTypes) => {
const newTypes = [...prevTypes];
// Update next level type
if (currentOrg.metadata?.govt_org_children_type) {
if (index === newTypes.length) {
newTypes.push(currentOrg.metadata.govt_org_children_type);
} else {
newTypes[index + 1] = currentOrg.metadata.govt_org_children_type;
}
}
return newTypes;
});
}
}
}, [selectedLevels, setOrgTypes]);

const options = useMemo(() => {
return getOrganizationOptions(availableOrgs?.results || []);
}, [availableOrgs?.results, getOrganizationOptions]);

if (skip) return null;
const orgType = orgTypes[index];

return (
<div className="gap-3 flex flex-col sm:flex-row">
<Select
value={selectedFacilityType?.text || ""}
onValueChange={(value) => {
setSelectedFacilityType(
FACILITY_TYPES.find((type) => type.text === value),
);
onChange({
facility_type: FACILITY_TYPES.find((type) => type.text === value)
?.id,
});
}}
>
<SelectTrigger className="overflow-hidden sm:min-w-56 sm:max-w-60 w-[calc(100vw-1rem)]">
<SelectValue placeholder={t("select_facility_type")} />
</SelectTrigger>
<SelectContent>
{FACILITY_TYPES.map((type) => (
<SelectItem key={type.id} value={type.text}>
{type.text}
</SelectItem>
))}
</SelectContent>
</Select>
{govtOrgLevels.map((_, index) =>
RenderOrganizationLevel(selectedLevels[index] || undefined, index),
)}
<Button
onClick={clearSelections}
variant="white"
disabled={
selectedLevels.length === 0 && selectedFacilityType === undefined
<Autocomplete
key={`dropdown-${index}`}
popoverClassName="sm:min-w-64 sm:max-w-72 w-[calc(100vw-2rem)]"
value={selectedLevels[index]?.id || ""}
options={options}
onChange={(value: string) => {
const selectedOrg = availableOrgs?.results.find(
(org) => org.id === value,
);

if (selectedOrg) {
onChange({ organization: selectedOrg.id }, index);
setLevelSearch("");
}
>
{t("clear")}
</Button>
</div>
}}
onSearch={(value) => setLevelSearch(value)}
placeholder={
orgType
? t("select") +
" " +
t(`SYSTEM__govt_org_type__${orgType?.toLowerCase()}`)
: t("select_previous")
}
disabled={index > selectedLevels.length}
align="start"
/>
);
}