feat: vitual lazy load on combobox

This commit is contained in:
2026-06-17 02:32:15 +05:30
parent 1325afdab2
commit db11a512bf
12 changed files with 592 additions and 99 deletions

View File

@@ -0,0 +1,74 @@
'use client';
import { AsyncCombobox, usePaginatedLookup } from '@/components/async-combobox';
import type { AsyncComboboxOption } from '@/components/async-combobox';
import { roleService } from '@/services/api';
import type { Role } from '@/types';
import { roleKeys } from '@/app/(modules)/roles/queries/roleKeys';
import type { RoleComboboxProps } from './RoleCombobox.types';
const ROLE_PAGE_SIZE = 20;
function parseRoleId(value: string) {
const roleId = Number(value);
return Number.isFinite(roleId) ? roleId : null;
}
function mapRoleOption(role: Role): AsyncComboboxOption {
return {
value: String(role.id),
label: role.display_name || role.name,
};
}
export function RoleCombobox({
value,
onValueChange,
enabled = true,
disabled = false,
portalContainer,
}: RoleComboboxProps) {
const lookup = usePaginatedLookup<Role>({
enabled,
selectedValue: value,
pageSize: ROLE_PAGE_SIZE,
queryKey: (searchTerm) =>
[
...roleKeys.lists(),
'async-lookup',
searchTerm,
ROLE_PAGE_SIZE,
] as const,
queryFn: ({ searchTerm, skip, limit }) =>
roleService.getRoles({
search_term: searchTerm || undefined,
skip,
limit,
effective_status: true,
sort_by: 'display_name',
sort_order: 'asc',
}),
mapOption: mapRoleOption,
resolveSelected: (roleId) => {
const numericRoleId = parseRoleId(roleId);
return numericRoleId ? roleService.getRoleById(numericRoleId) : Promise.resolve(null);
},
resolveSelectedQueryKey: (roleId) => {
const numericRoleId = parseRoleId(roleId);
return numericRoleId ? roleKeys.detail(numericRoleId) : [...roleKeys.details(), 'invalid', roleId];
},
});
return (
<AsyncCombobox
lookup={lookup}
onValueChange={onValueChange}
disabled={disabled}
placeholder="Select role"
searchPlaceholder="Search roles..."
emptyMessage="No roles found."
portalContainer={portalContainer}
/>
);
}