Fix Filter Dropdown UX Issue - Load Initial Options (#13858)

* move the filter component to molecules

* load initial options
This commit is contained in:
tanjiro 2025-08-22 06:56:49 +09:00 • committed by GitHub
parent 67621d3381
commit 0aa1371903
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 5 deletions

View file

@ -7,8 +7,8 @@ import { Button } from "@tremor/react"
import KeyInfoView from "./templates/key_info_view"
import { Tooltip } from "antd"
import { Team, KeyResponse } from "./key_team_helpers/key_list"
import FilterComponent from "./common_components/filter"
import { FilterOption } from "./common_components/filter"
import FilterComponent from "./molecules/filter"
import { FilterOption } from "./molecules/filter"
import { keyListCall, Organization, userListCall } from "./networking"
import { createTeamSearchFunction } from "./key_team_helpers/team_search_fn"
import { createOrgSearchFunction } from "./key_team_helpers/organization_search_fn"

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback } from "react";
import React, { useState, useCallback, useEffect } from "react";
import { Button, Input, Select } from "antd";
import { FilterIcon } from "@heroicons/react/outline";
import debounce from "lodash/debounce";
@ -43,6 +43,9 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
const [searchInputValueMap, setSearchInputValueMap] = useState<{
[key: string]: string;
}>({});
const [initialOptionsLoaded, setInitialOptionsLoaded] = useState<{
[key: string]: boolean;
}>({});
const debouncedSearch = useCallback(
debounce(async (value: string, option: FilterOption) => {
@ -62,6 +65,36 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
[]
);
// Load initial options for searchable filters
const loadInitialOptions = useCallback(async (option: FilterOption) => {
if (!option.isSearchable || !option.searchFn || initialOptionsLoaded[option.name]) return;
setSearchLoadingMap((prev) => ({ ...prev, [option.name]: true }));
setInitialOptionsLoaded((prev) => ({ ...prev, [option.name]: true }));
try {
// Load initial options with empty search to get some default results
const results = await option.searchFn("");
setSearchOptionsMap((prev) => ({ ...prev, [option.name]: results }));
} catch (error) {
console.error("Error loading initial options:", error);
setSearchOptionsMap((prev) => ({ ...prev, [option.name]: [] }));
} finally {
setSearchLoadingMap((prev) => ({ ...prev, [option.name]: false }));
}
}, [initialOptionsLoaded]);
// Load initial options when filters are shown
useEffect(() => {
if (showFilters) {
options.forEach(option => {
if (option.isSearchable && !initialOptionsLoaded[option.name]) {
loadInitialOptions(option);
}
});
}
}, [showFilters, options, loadInitialOptions, initialOptionsLoaded]);
const handleFilterChange = (name: string, value: string) => {
const newValues = {
...tempValues,
@ -80,6 +113,13 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
onResetFilters();
};
// Handle dropdown open to load initial options
const handleDropdownVisibleChange = (open: boolean, option: FilterOption) => {
if (open && option.isSearchable && !initialOptionsLoaded[option.name]) {
loadInitialOptions(option);
}
};
// Define the order of filters
const orderedFilters = [
"Team ID",
@ -125,6 +165,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
placeholder={`Search ${option.label || option.name}...`}
value={tempValues[option.name] || undefined}
onChange={(value) => handleFilterChange(option.name, value)}
onDropdownVisibleChange={(open) => handleDropdownVisibleChange(open, option)}
onSearch={(value) => {
setSearchInputValueMap((prev) => ({
...prev,
@ -138,6 +179,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
loading={searchLoadingMap[option.name]}
options={searchOptionsMap[option.name] || []}
allowClear
notFoundContent={searchLoadingMap[option.name] ? "Loading..." : "No results found"}
/>
) : option.options ? (
<Select

View file

@ -18,8 +18,8 @@ import KeyInfoView from "../templates/key_info_view"
import { SessionView } from "./SessionView"
import { VectorStoreViewer } from "./VectorStoreViewer"
import { GuardrailViewer } from "./GuardrailViewer"
import FilterComponent from "../common_components/filter"
import { FilterOption } from "../common_components/filter"
import FilterComponent from "../molecules/filter"
import { FilterOption } from "../molecules/filter"
import { useLogFilterLogic } from "./log_filter_logic"
import { fetchAllKeyAliases } from "../key_team_helpers/filter_helpers"
import { Tab, TabGroup, TabList, TabPanels, TabPanel, Text, Switch } from "@tremor/react"