/**
* 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;
});
});
}