diff --git a/src/frontend/src/components/shared/DynamicDropdown.tsx b/src/frontend/src/components/shared/DynamicDropdown.tsx
index 914b7553..13a0b6e9 100644
--- a/src/frontend/src/components/shared/DynamicDropdown.tsx
+++ b/src/frontend/src/components/shared/DynamicDropdown.tsx
@@ -5,48 +5,25 @@ import { DynamicFieldOption, fetchFieldOptions } from '../../services/api';
interface DynamicDropdownProps {
endpoint: string;
value: string;
- onChange: (value: string) => void;
+ onChange: (value: string, label?: string) => void;
placeholder?: string;
- allLabel?: string;
}
-const GROUP_HEADER_PREFIX = '__group__';
-
const buildOptions = (
options: DynamicFieldOption[],
- allLabel: string
): DropdownListOption[] => {
- const built: DropdownListOption[] = [{ value: '', label: allLabel }];
- let previousGroup: string | null = null;
-
- options.forEach((option, index) => {
- if (option.group && option.group !== previousGroup) {
- previousGroup = option.group;
- built.push({
- value: `${GROUP_HEADER_PREFIX}${option.group}:${index}`,
- label: option.group,
- disabled: true,
- });
- } else if (!option.group) {
- previousGroup = null;
- }
-
- built.push({
- value: option.value,
- label: option.label,
- description: option.description,
- });
- });
-
- return built;
+ return options.map((option) => ({
+ value: option.value,
+ label: option.label,
+ description: option.description,
+ }));
};
export const DynamicDropdown = ({
endpoint,
value,
onChange,
- placeholder = 'Select an option',
- allLabel = 'All',
+ placeholder = 'Select...',
}: DynamicDropdownProps) => {
const [options, setOptions] = useState
([]);
const [isLoading, setIsLoading] = useState(true);
@@ -87,28 +64,20 @@ export const DynamicDropdown = ({
const dropdownOptions = useMemo(() => {
if (isLoading) {
- return [
- { value: '', label: allLabel },
- { value: '__loading', label: 'Loading...', disabled: true },
- ];
+ return [{ value: '__loading', label: 'Loading...', disabled: true }];
}
if (loadError) {
- return [
- { value: '', label: allLabel },
- { value: '__error', label: loadError, disabled: true },
- ];
+ return [{ value: '__error', label: loadError, disabled: true }];
}
- return buildOptions(options, allLabel);
- }, [allLabel, isLoading, loadError, options]);
+ return buildOptions(options);
+ }, [isLoading, loadError, options]);
const handleChange = (nextValue: string[] | string) => {
const normalized = Array.isArray(nextValue) ? nextValue[0] ?? '' : nextValue;
- if (normalized.startsWith(GROUP_HEADER_PREFIX)) {
- return;
- }
- onChange(normalized);
+ const match = options.find((opt) => opt.value === normalized);
+ onChange(normalized, match?.label);
};
return (
diff --git a/src/frontend/src/components/shared/SearchFieldRenderer.tsx b/src/frontend/src/components/shared/SearchFieldRenderer.tsx
index aa2c4e2d..54432cdb 100644
--- a/src/frontend/src/components/shared/SearchFieldRenderer.tsx
+++ b/src/frontend/src/components/shared/SearchFieldRenderer.tsx
@@ -6,7 +6,7 @@ import { DynamicDropdown } from './DynamicDropdown';
interface SearchFieldRendererProps {
field: MetadataSearchField;
value: string | number | boolean;
- onChange: (value: string | number | boolean) => void;
+ onChange: (value: string | number | boolean, label?: string) => void;
onSubmit?: () => void;
}
@@ -104,9 +104,8 @@ export const SearchFieldRenderer = ({ field, value, onChange, onSubmit }: Search
onChange(v)}
- placeholder={field.placeholder || 'Select an option'}
- allLabel="All"
+ onChange={(v, label) => onChange(v, label)}
+ placeholder={field.placeholder || 'Select...'}
/>
);
diff --git a/src/frontend/src/hooks/useSearch.ts b/src/frontend/src/hooks/useSearch.ts
index 90e29a71..dc3acaf4 100644
--- a/src/frontend/src/hooks/useSearch.ts
+++ b/src/frontend/src/hooks/useSearch.ts
@@ -1,4 +1,4 @@
-import { useState, useCallback, useRef, useEffect } from 'react';
+import { useState, useCallback, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Book, AppConfig, AdvancedFilterState, ContentType } from '../types';
import { searchBooks, searchMetadata, AuthenticationError } from '../services/api';
@@ -41,7 +41,8 @@ interface UseSearchReturn {
resetSortFilter: () => void;
// Universal mode search field values
searchFieldValues: SearchFieldValues;
- updateSearchFieldValue: (key: string, value: string | number | boolean) => void;
+ updateSearchFieldValue: (key: string, value: string | number | boolean, label?: string) => void;
+ searchFieldLabels: Record;
// Pagination (universal mode only)
hasMore: boolean;
isLoadingMore: boolean;
@@ -70,6 +71,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
// Universal mode: provider-specific search field values
const [searchFieldValues, setSearchFieldValues] = useState({});
+ const [searchFieldLabels, setSearchFieldLabels] = useState>({});
// Pagination state (universal mode only)
const [currentPage, setCurrentPage] = useState(1);
@@ -83,31 +85,18 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
sort: string;
fieldValues: SearchFieldValues;
} | null>(null);
- const previousContentTypeRef = useRef(contentType);
-
- // Switching media type starts a new search session.
- useEffect(() => {
- if (previousContentTypeRef.current === contentType) {
- return;
- }
-
- previousContentTypeRef.current = contentType;
- setBooks([]);
- setLastSearchQuery('');
- setSearchFieldValues({});
- setHasMore(false);
- setTotalFound(0);
- setCurrentPage(1);
- lastSearchParamsRef.current = null;
- onSearchReset?.();
- }, [contentType, onSearchReset]);
const updateAdvancedFilters = useCallback((updates: Partial) => {
setAdvancedFilters(prev => ({ ...prev, ...updates }));
}, []);
- const updateSearchFieldValue = useCallback((key: string, value: string | number | boolean) => {
+ const updateSearchFieldValue = useCallback((key: string, value: string | number | boolean, label?: string) => {
setSearchFieldValues(prev => ({ ...prev, [key]: value }));
+ if (label !== undefined) {
+ setSearchFieldLabels(prev => ({ ...prev, [key]: label }));
+ } else if (!value) {
+ setSearchFieldLabels(prev => { const next = { ...prev }; delete next[key]; return next; });
+ }
}, []);
const resetSortFilter = useCallback(() => {
@@ -319,6 +308,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
// Universal mode search field values
searchFieldValues,
updateSearchFieldValue,
+ searchFieldLabels,
// Pagination (universal mode only)
hasMore,
isLoadingMore,