import React from 'react'; import { HiMagnifyingGlass, HiXMark, HiAdjustmentsHorizontal } from 'react-icons/hi2'; import Typography from '../ui/Typography'; import { useDebounce } from '../../hooks/use-debounced-value'; /** * Entity filter configuration */ export interface EntityFiltersConfig { search: string; entityType: string; dateRange: { start: Date | null; end: Date | null; }; sessionId: string; } /** * Props for EntityFilters component */ export interface EntityFiltersProps { filters: EntityFiltersConfig; onFiltersChange: (filters: Partial) => void; entityTypes: string[]; onClearFilters: () => void; totalEntities: number; filteredEntities: number; sessionIds?: string[]; } /** * Entity filtering component with search, type, and date range filters */ export const EntityFilters: React.FC = ({ filters, onFiltersChange, entityTypes, onClearFilters, totalEntities, filteredEntities, sessionIds = [], }) => { const [searchValue, setSearchValue] = React.useState(filters.search); const [showAdvanced, setShowAdvanced] = React.useState(false); const [dateError, setDateError] = React.useState(null); const debouncedSearch = useDebounce(searchValue, 300); React.useEffect(() => { if (debouncedSearch !== filters.search) { onFiltersChange({ search: debouncedSearch }); } }, [debouncedSearch, filters.search, onFiltersChange]); const handleDateRangeChange = React.useCallback((field: 'start' | 'end', value: string) => { const date = value ? new Date(value) : null; const currentDateRange = filters.dateRange; const newDateRange = { ...currentDateRange }; newDateRange[field] = date; if (newDateRange.start && newDateRange.end && newDateRange.start > newDateRange.end) { setDateError('End date must be after start date'); return; } setDateError(null); onFiltersChange({ dateRange: newDateRange }); }, [filters.dateRange, onFiltersChange]); const handlePresetDateRange = React.useCallback((preset: 'today' | 'week' | 'month' | '30days') => { const end = new Date(); const start = new Date(); switch (preset) { case 'today': start.setHours(0, 0, 0, 0); end.setHours(23, 59, 59, 999); break; case 'week': start.setDate(end.getDate() - 7); break; case 'month': start.setMonth(end.getMonth() - 1); break; case '30days': start.setDate(end.getDate() - 30); break; } onFiltersChange({ dateRange: { start, end } }); }, [onFiltersChange]); const removeBadge = React.useCallback((filterType: keyof EntityFiltersConfig) => { switch (filterType) { case 'search': setSearchValue(''); onFiltersChange({ search: '' }); break; case 'entityType': onFiltersChange({ entityType: '' }); break; case 'sessionId': onFiltersChange({ sessionId: '' }); break; case 'dateRange': onFiltersChange({ dateRange: { start: null, end: null } }); break; } }, [onFiltersChange]); const hasActiveFilters = React.useMemo(() => { return ( filters.search || filters.entityType || filters.sessionId || filters.dateRange.start || filters.dateRange.end ); }, [filters]); const formatDateValue = React.useCallback((date: Date | null) => { if (!date) return ''; return date.toISOString().split('T')[0]; }, []); const formatDateRange = React.useCallback((start: Date | null, end: Date | null) => { if (!start && !end) return ''; const startStr = start ? start.toLocaleDateString() : ''; const endStr = end ? end.toLocaleDateString() : ''; if (start && end) return `${startStr} to ${endStr}`; if (start) return `From ${startStr}`; if (end) return `Until ${endStr}`; return ''; }, []); return (
{/* Search and basic filters */}
{/* Search input */}
setSearchValue(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 focus:ring-2 focus:ring-[#5599fe] focus:outline-none" aria-label="Search entities by name, ID, or transaction" /> {searchValue && ( )}
{/* Entity type filter */}
{/* Session filter (only show if multiple sessions) */} {sessionIds.length > 1 && (
)} {/* Advanced filters toggle */}
{/* Advanced filters */} {showAdvanced && (
{/* Date range filter */}
Date Range
handleDateRangeChange('start', e.target.value)} className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-[#5599fe] focus:outline-none" aria-label="Start Date" />
handleDateRangeChange('end', e.target.value)} className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-[#5599fe] focus:outline-none" aria-label="End Date" />
{dateError && ( {dateError} )} {/* Date presets */}
)} {/* Active filter badges and results */}
Showing {filteredEntities} of {totalEntities} entities {hasActiveFilters && ( <> {filters.search && ( Search: {filters.search} )} {filters.entityType && ( Type: {filters.entityType} )} {filters.sessionId && ( Session: {filters.sessionId} )} {(filters.dateRange.start || filters.dateRange.end) && ( Date Range: {formatDateRange(filters.dateRange.start, filters.dateRange.end)} )} )}
{hasActiveFilters && ( )}
{/* Search suggestions */} {searchValue && searchValue.length > 0 && (
Search in names Search in IDs Search in transaction IDs
)}
); };