/** * Search utility functions for enhanced filtering and searching */ /** * Performs a fuzzy search on a string, checking if all search terms are present * @param searchTerm The search term to look for * @param targetString The string to search in * @returns true if all search terms are found in the target string */ export function fuzzySearch(searchTerm: string, targetString: string): boolean { if (!searchTerm.trim()) return true; const searchWords = searchTerm.toLowerCase().split(/\s+/).filter(word => word.length > 0); const target = targetString.toLowerCase(); return searchWords.every(word => target.includes(word)); } /** * Performs a multi-field search across multiple string fields * @param searchTerm The search term to look for * @param fields Array of strings to search in * @returns true if the search term is found in any of the fields */ export function multiFieldSearch(searchTerm: string, fields: (string | number)[]): boolean { if (!searchTerm.trim()) return true; const searchLower = searchTerm.toLowerCase(); return fields.some(field => { const fieldStr = String(field).toLowerCase(); return fieldStr.includes(searchLower); }); } /** * Highlights search terms in a string by wrapping them with HTML tags * @param text The text to highlight * @param searchTerm The term to highlight * @param className CSS class to apply to highlighted text * @returns HTML string with highlighted terms */ export function highlightSearchTerm( text: string, searchTerm: string, className: string = 'bg-yellow-200' ): string { if (!searchTerm.trim()) return text; const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); return text.replace(regex, `$1`); } /** * Debounce function to limit the rate of function calls * @param func The function to debounce * @param delay The delay in milliseconds * @returns Debounced function */ export function debounce any>( func: T, delay: number ): (...args: Parameters) => void { let timeoutId: NodeJS.Timeout; return (...args: Parameters) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; } /** * Creates a search filter function for arrays of objects * @param searchTerm The search term * @param searchFields Array of field names to search in * @returns Filter function that can be used with Array.filter() */ export function createSearchFilter>( searchTerm: string, searchFields: (keyof T)[] ) { return (item: T): boolean => { if (!searchTerm.trim()) return true; const fieldsToSearch = searchFields.map(field => item[field]); return multiFieldSearch(searchTerm, fieldsToSearch); }; } /** * Sorts an array of objects by a specific field * @param array The array to sort * @param field The field to sort by * @param direction The sort direction * @returns Sorted array */ export function sortByField>( array: T[], field: keyof T, direction: 'asc' | 'desc' = 'asc' ): T[] { return [...array].sort((a, b) => { const aValue = a[field]; const bValue = b[field]; if (typeof aValue === 'string' && typeof bValue === 'string') { const comparison = aValue.localeCompare(bValue); return direction === 'asc' ? comparison : -comparison; } if (typeof aValue === 'number' && typeof bValue === 'number') { const comparison = aValue - bValue; return direction === 'asc' ? comparison : -comparison; } if (aValue instanceof Date && bValue instanceof Date) { const comparison = aValue.getTime() - bValue.getTime(); return direction === 'asc' ? comparison : -comparison; } // Fallback to string comparison const comparison = String(aValue).localeCompare(String(bValue)); return direction === 'asc' ? comparison : -comparison; }); } /** * Filters an array based on multiple filter criteria * @param array The array to filter * @param filters Object containing filter criteria * @returns Filtered array */ export function applyFilters>( array: T[], filters: Record ): T[] { return array.filter(item => { return Object.entries(filters).every(([key, value]) => { if (value === 'all' || value === '' || value == null) return true; return item[key] === value; }); }); }