import React, { useState, useEffect, useMemo } from 'react'; import { Box, Text, useInput } from 'ink'; import TextInput from 'ink-text-input'; import SelectInput from 'ink-select-input'; import { getFilterService, FilterCriteria, SearchOptions, FilterResult } from '../services/filter-service.js'; export interface SearchFilterProps { data: T[]; onFilteredData: (result: FilterResult) => void; placeholder?: string; searchFields?: string[]; enableFacets?: boolean; enableSavedFilters?: boolean; defaultCriteria?: FilterCriteria; defaultOptions?: SearchOptions; visible?: boolean; onClose?: () => void; } export interface FilterPanelState { mode: 'search' | 'filters' | 'saved' | 'facets'; searchTerm: string; selectedFilters: Record; sortBy: string; sortOrder: 'asc' | 'desc'; showAdvanced: boolean; } export function SearchFilter({ data, onFilteredData, placeholder = 'Search...', searchFields, enableFacets = true, enableSavedFilters = true, defaultCriteria = {}, defaultOptions = {}, visible = true, onClose }: SearchFilterProps) { const [state, setState] = useState({ mode: 'search', searchTerm: '', selectedFilters: {}, sortBy: '', sortOrder: 'asc', showAdvanced: false }); const [filterResult, setFilterResult] = useState>({ items: data, totalCount: data.length, filteredCount: data.length, matches: [], facets: {}, suggestions: [] }); const filterService = getFilterService(); // Apply filters when data or criteria change useEffect(() => { const criteria: FilterCriteria = { search: state.searchTerm, fields: searchFields, sortBy: state.sortBy || undefined, sortOrder: state.sortOrder, ...state.selectedFilters, ...defaultCriteria }; const result = filterService.filter(data, criteria, defaultOptions); setFilterResult(result); onFilteredData(result); }, [data, state, searchFields, defaultCriteria, defaultOptions, onFilteredData]); // Handle keyboard input useInput((input, key) => { if (!visible) return; if (key.escape) { onClose?.(); } else if (key.tab) { // Cycle through modes const modes: FilterPanelState['mode'][] = ['search', 'filters', 'facets', 'saved']; const currentIndex = modes.indexOf(state.mode); const nextIndex = (currentIndex + 1) % modes.length; setState(prev => ({ ...prev, mode: modes[nextIndex] })); } else if (input === 'a' && state.mode === 'search') { setState(prev => ({ ...prev, showAdvanced: !prev.showAdvanced })); } else if (input === 's' && state.mode === 'filters') { // Save current filter saveCurrentFilter(); } else if (input === 'c' && state.mode === 'search') { // Clear search setState(prev => ({ ...prev, searchTerm: '' })); } }); const saveCurrentFilter = () => { if (state.searchTerm || Object.keys(state.selectedFilters).length > 0) { const criteria: FilterCriteria = { search: state.searchTerm, fields: searchFields, sortBy: state.sortBy || undefined, sortOrder: state.sortOrder, ...state.selectedFilters }; const filterId = filterService.saveFilter( `Filter ${new Date().toLocaleTimeString()}`, criteria, defaultOptions, 'Auto-saved filter' ); // Could show a notification here } }; if (!visible) return null; return ( {/* Header */} 🔍 Search & Filter {state.mode.toUpperCase()} | Tab: Switch | Esc: Close {/* Mode Content */} {state.mode === 'search' && ( setState(prev => ({ ...prev, searchTerm: term }))} suggestions={filterResult.suggestions} showAdvanced={state.showAdvanced} placeholder={placeholder} /> )} {state.mode === 'filters' && ( setState(prev => ({ ...prev, selectedFilters: filters }))} sortBy={state.sortBy} sortOrder={state.sortOrder} onSortChange={(sortBy, sortOrder) => setState(prev => ({ ...prev, sortBy, sortOrder })) } /> )} {state.mode === 'facets' && enableFacets && ( setState(prev => ({ ...prev, selectedFilters: filters }))} /> )} {state.mode === 'saved' && enableSavedFilters && ( { setState(prev => ({ ...prev, searchTerm: criteria.search || '', selectedFilters: { type: criteria.type, status: criteria.status, tags: criteria.tags, ...criteria.customFilters }, sortBy: criteria.sortBy || '', sortOrder: criteria.sortOrder || 'asc' })); }} /> )} {/* Results Summary */} 📊 Results: {filterResult.filteredCount} of {filterResult.totalCount} {filterResult.matches.length > 0 && ( | {filterResult.matches.length} matches )} {/* Quick Actions */} Actions: [A]dvanced • [C]lear • [S]ave Filter • [Tab] Switch Mode ); } // Search Panel Component interface SearchPanelProps { searchTerm: string; onSearchChange: (term: string) => void; suggestions: string[]; showAdvanced: boolean; placeholder: string; } function SearchPanel({ searchTerm, onSearchChange, suggestions, showAdvanced, placeholder }: SearchPanelProps) { const [inputFocused, setInputFocused] = useState(true); return ( Search: setInputFocused(false)} /> {showAdvanced && ( Advanced Search Options: • Use quotes for exact phrases: "exact phrase" • Use wildcards: agent* or *task • Use regex: /pattern/flags • Field search: name:agent OR status:running )} {suggestions.length > 0 && ( 💡 Suggestions: {suggestions.slice(0, 5).map((suggestion, index) => ( • {suggestion} ))} )} ); } // Filters Panel Component interface FiltersPanelProps { selectedFilters: Record; onFiltersChange: (filters: Record) => void; sortBy: string; sortOrder: 'asc' | 'desc'; onSortChange: (sortBy: string, sortOrder: 'asc' | 'desc') => void; } function FiltersPanel({ selectedFilters, onFiltersChange, sortBy, sortOrder, onSortChange }: FiltersPanelProps) { const [selectedField, setSelectedField] = useState('type'); const filterFields = [ { label: 'Type', value: 'type' }, { label: 'Status', value: 'status' }, { label: 'Tags', value: 'tags' }, { label: 'Date Range', value: 'dateRange' } ]; const sortFields = [ { label: 'Name', value: 'name' }, { label: 'Created Date', value: 'createdAt' }, { label: 'Updated Date', value: 'updatedAt' }, { label: 'Status', value: 'status' } ]; return ( Active Filters: {Object.keys(selectedFilters).length === 0 ? ( No active filters ) : ( {Object.entries(selectedFilters).map(([key, value]) => ( • {key}: {Array.isArray(value) ? value.join(', ') : String(value)} ))} )} Sort By: {sortBy || 'None'} ({sortOrder}) Use facets panel to add filters or type field:value in search ); } // Facets Panel Component interface FacetsPanelProps { facets: Record; selectedFilters: Record; onFiltersChange: (filters: Record) => void; } function FacetsPanel({ facets, selectedFilters, onFiltersChange }: FacetsPanelProps) { const facetEntries = Object.entries(facets); if (facetEntries.length === 0) { return ( No facets available ); } return ( Filter by Categories: {facetEntries.map(([facetName, facet]) => ( {facetName.toUpperCase()}: {facet.values.slice(0, 5).map((item: any, index: number) => ( • {item.value} ({item.count}) ))} ))} Click values to filter (interactive mode coming soon) ); } // Saved Filters Panel Component interface SavedFiltersPanelProps { onLoadFilter: (criteria: FilterCriteria) => void; } function SavedFiltersPanel({ onLoadFilter }: SavedFiltersPanelProps) { const filterService = getFilterService(); const savedFilters = useMemo(() => filterService.getSavedFilters(), []); const stats = useMemo(() => filterService.getFilterStats(), []); if (savedFilters.length === 0) { return ( No saved filters Create filters and press 'S' to save them ); } return ( Saved Filters ({savedFilters.length}): {savedFilters.slice(0, 5).map((filter, index) => ( {filter.name} Used {filter.useCount} times • Last: {filter.lastUsed.toLocaleDateString()} {filter.description && ( {filter.description} )} ))} 💡 Recent Searches: {stats.recentSearches.slice(0, 3).map((search, index) => ( • "{search}" ))} ); } export default SearchFilter;