-
Notifications
You must be signed in to change notification settings - Fork 649
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
Fix: Facility Organization Dropdown #10104
Changes from all commits
2331940
14813fd
84bf83e
7e0f0f2
5979374
40abd27
91951d8
6ea8e94
d739539
11c53da
af228ea
e1815dd
695fd55
e33bbc4
9c3c994
7d713a9
9099f66
0825a41
30253ca
b8dd53c
25e5b29
0ff9a39
293d230
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,12 +1,11 @@ | ||||||
import { useQuery } from "@tanstack/react-query"; | ||||||
import { useQueries, useQuery } from "@tanstack/react-query"; | ||||||
import { Building } from "lucide-react"; | ||||||
import { useState } from "react"; | ||||||
import { useTranslation } from "react-i18next"; | ||||||
|
||||||
import CareIcon from "@/CAREUI/icons/CareIcon"; | ||||||
|
||||||
import Autocomplete from "@/components/ui/autocomplete"; | ||||||
import { Badge } from "@/components/ui/badge"; | ||||||
import { Button } from "@/components/ui/button"; | ||||||
import { Label } from "@/components/ui/label"; | ||||||
|
||||||
import routes from "@/Utils/request/api"; | ||||||
|
@@ -40,47 +39,40 @@ export default function FacilityOrganizationSelector( | |||||
const [selectedOrganization, setSelectedOrganization] = | ||||||
useState<FacilityOrganization | null>(null); | ||||||
|
||||||
const { data: getAllOrganizations } = useQuery<FacilityOrganizationResponse>({ | ||||||
const { data: rootOrganizations } = useQuery<FacilityOrganizationResponse>({ | ||||||
queryKey: ["organizations-root"], | ||||||
queryFn: query(routes.facilityOrganization.list, { | ||||||
pathParams: { facilityId }, | ||||||
queryParams: { | ||||||
parent: "", | ||||||
}, | ||||||
queryParams: { parent: "" }, | ||||||
}), | ||||||
}); | ||||||
|
||||||
const { data: currentLevelOrganizations } = useQuery<{ | ||||||
results: FacilityOrganization[]; | ||||||
}>({ | ||||||
queryKey: [ | ||||||
"organizations-current", | ||||||
selectedLevels[selectedLevels.length - 1]?.id, | ||||||
], | ||||||
queryFn: query(routes.facilityOrganization.list, { | ||||||
pathParams: { facilityId }, | ||||||
queryParams: { | ||||||
parent: selectedLevels[selectedLevels.length - 1]?.id, | ||||||
}, | ||||||
}), | ||||||
enabled: selectedLevels.length > 0, | ||||||
const organizationQueries = useQueries({ | ||||||
queries: selectedLevels.map((level, _index) => ({ | ||||||
queryKey: ["organizations", level.id], | ||||||
queryFn: query(routes.facilityOrganization.list, { | ||||||
pathParams: { facilityId }, | ||||||
queryParams: { parent: level.id }, | ||||||
}), | ||||||
enabled: !!level.id, | ||||||
})), | ||||||
}); | ||||||
|
||||||
const handleLevelChange = (value: string, level: number) => { | ||||||
const orgList = | ||||||
level === 0 | ||||||
? getAllOrganizations?.results | ||||||
: currentLevelOrganizations?.results; | ||||||
let orgList: FacilityOrganization[] | undefined; | ||||||
|
||||||
if (level === 0) { | ||||||
orgList = rootOrganizations?.results; | ||||||
} else if (level - 1 < organizationQueries.length) { | ||||||
orgList = organizationQueries[level - 1].data?.results; | ||||||
} | ||||||
|
||||||
const selectedOrg = orgList?.find((org) => org.id === value); | ||||||
if (!selectedOrg) return; | ||||||
|
||||||
const newLevels = selectedLevels.slice(0, level); | ||||||
newLevels.push(selectedOrg); | ||||||
setSelectedLevels(newLevels); | ||||||
setSelectedOrganization(selectedOrg); | ||||||
|
||||||
// Always update the selected value, regardless of children | ||||||
onChange(selectedOrg.id); | ||||||
}; | ||||||
|
||||||
|
@@ -104,92 +96,86 @@ export default function FacilityOrganizationSelector( | |||||
onChange(lastOrg.id); | ||||||
} else { | ||||||
setSelectedOrganization(null); | ||||||
onChange(""); | ||||||
} | ||||||
}; | ||||||
|
||||||
const renderOrganizationLevel = (level: number) => { | ||||||
let orgList: FacilityOrganization[] | undefined; | ||||||
|
||||||
if (level === 0) { | ||||||
orgList = rootOrganizations?.results; | ||||||
} else if (level - 1 < organizationQueries.length) { | ||||||
orgList = organizationQueries[level - 1].data?.results; | ||||||
} | ||||||
|
||||||
const getDropdownLabel = () => { | ||||||
if (level < selectedLevels.length) { | ||||||
return selectedLevels[level].name; | ||||||
} | ||||||
return level === 0 ? t("select_department") : t("select_sub_department"); | ||||||
}; | ||||||
|
||||||
return ( | ||||||
<div className="group flex items-center gap-1.5"> | ||||||
{level > 0 && ( | ||||||
<CareIcon | ||||||
icon="l-arrow-right" | ||||||
className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" | ||||||
/> | ||||||
)} | ||||||
<div className="flex-1 flex items-center gap-2"> | ||||||
<div className="flex-1"> | ||||||
<Autocomplete | ||||||
value={selectedLevels[level]?.id} | ||||||
options={getOrganizationOptions(orgList)} | ||||||
onChange={(value) => handleLevelChange(value, level)} | ||||||
placeholder={getDropdownLabel()} | ||||||
/> | ||||||
</div> | ||||||
{level > 0 && level < selectedLevels.length && ( | ||||||
<div | ||||||
className="cursor-pointer p-1 hover:bg-gray-100 rounded-sm opacity-0 group-hover:opacity-100 transition-opacity" | ||||||
onClick={() => handleEdit(level)} | ||||||
> | ||||||
<CareIcon icon="l-pen" className="h-4 w-4 text-gray-500" /> | ||||||
</div> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace edit pencil with dropdown arrow per PR objectives. The PR objectives mention replacing the edit pencil with a dropdown arrow, but the implementation still uses the pencil icon. Apply this diff to align with PR objectives: - <CareIcon icon="l-pen" className="h-4 w-4 text-gray-500" />
+ <CareIcon icon="l-chevron-down" className="h-4 w-4 text-gray-500" /> 📝 Committable suggestion
Suggested change
|
||||||
)} | ||||||
</div> | ||||||
</div> | ||||||
); | ||||||
}; | ||||||
|
||||||
return ( | ||||||
<> | ||||||
<Label className="mb-2"> | ||||||
<Label className="mb-2 block"> | ||||||
{t("select_department")} | ||||||
{required && <span className="text-red-500">*</span>} | ||||||
</Label> | ||||||
<div className="space-y-4"> | ||||||
{/* Selected Organization Display */} | ||||||
<div className="space-y-3"> | ||||||
{selectedOrganization && ( | ||||||
<div className="rounded-md border p-3 bg-gray-50"> | ||||||
<div className="flex items-center justify-between"> | ||||||
<div> | ||||||
<p className="font-medium">{selectedOrganization.name}</p> | ||||||
{selectedOrganization.has_children && ( | ||||||
<p className="text-sm text-gray-500"> | ||||||
You can select a sub-department or keep this selection | ||||||
</p> | ||||||
)} | ||||||
</div> | ||||||
<div className="flex items-center gap-3 rounded-md border border-sky-100 bg-sky-50/50 p-2.5"> | ||||||
<Building className="h-4 w-4 text-sky-600 flex-shrink-0" /> | ||||||
<div className="flex-1 min-w-0"> | ||||||
<p className="font-medium text-sm text-sky-900 truncate"> | ||||||
{selectedOrganization.name} | ||||||
</p> | ||||||
{selectedOrganization.has_children && ( | ||||||
<Badge variant="outline">Has Sub-departments</Badge> | ||||||
<p className="text-xs text-sky-600"> | ||||||
{t("has_sub_departments")} | ||||||
</p> | ||||||
)} | ||||||
</div> | ||||||
</div> | ||||||
)} | ||||||
|
||||||
{/* Organization Hierarchy */} | ||||||
<div className="space-y-2"> | ||||||
<div className="space-y-1.5"> | ||||||
{selectedLevels.map((org, index) => ( | ||||||
<div key={org.id} className="flex items-center gap-2"> | ||||||
{index > 0 && ( | ||||||
<CareIcon | ||||||
icon="l-arrow-right" | ||||||
className="h-4 w-4 text-gray-400" | ||||||
/> | ||||||
)} | ||||||
<div className="flex-1"> | ||||||
<div className="flex gap-2"> | ||||||
<div className="flex items-center h-9 w-full rounded-md border border-gray-200 bg-white px-3 py-1 text-base shadow-sm"> | ||||||
{org.name} | ||||||
</div> | ||||||
<Button | ||||||
variant="ghost" | ||||||
size="icon" | ||||||
onClick={() => handleEdit(index)} | ||||||
type="button" | ||||||
> | ||||||
<CareIcon icon="l-pen" className="h-4 w-4" /> | ||||||
</Button> | ||||||
</div> | ||||||
</div> | ||||||
</div> | ||||||
<div key={org.id}>{renderOrganizationLevel(index)}</div> | ||||||
))} | ||||||
{(!selectedLevels.length || | ||||||
selectedLevels[selectedLevels.length - 1]?.has_children) && | ||||||
renderOrganizationLevel(selectedLevels.length)} | ||||||
</div> | ||||||
|
||||||
{/* Next Selection */} | ||||||
{(!selectedLevels.length || | ||||||
selectedLevels[selectedLevels.length - 1]?.has_children) && ( | ||||||
<div className="flex items-center gap-2"> | ||||||
{selectedLevels.length > 0 && ( | ||||||
<CareIcon | ||||||
icon="l-arrow-right" | ||||||
className="h-4 w-4 text-gray-400" | ||||||
/> | ||||||
)} | ||||||
<div className="flex-1"> | ||||||
<Autocomplete | ||||||
value="" | ||||||
options={getOrganizationOptions( | ||||||
selectedLevels.length === 0 | ||||||
? getAllOrganizations?.results | ||||||
: currentLevelOrganizations?.results, | ||||||
)} | ||||||
onChange={(value: string) => | ||||||
handleLevelChange(value, selectedLevels.length) | ||||||
} | ||||||
placeholder={`Select ${ | ||||||
selectedLevels.length ? "sub-department" : "Department" | ||||||
}...`} | ||||||
/> | ||||||
</div> | ||||||
</div> | ||||||
)} | ||||||
</div> | ||||||
</> | ||||||
); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error or loading states for sub-organization queries.
If a sub-organization query fails or is still loading, the user has no feedback. Consider displaying a loading indicator or error message for each query, ensuring a smoother user experience when fetching data asynchronously.