import { createTextMatcher } from "./text-search"; export { normalizeSearchText as normalizeColumnSearch } from "./text-search"; export type EntityColumnMetadata = { groupId?: string; keywords?: readonly string[]; }; export type EntityColumnGroup = { id: string; label: string; description?: string; }; export type EntityColumnOption = EntityColumnMetadata & { id: string; label: string; visible: boolean; hideable: boolean; }; /** Los IDs conservan identidad; la primera declaración de un grupo u opción prevalece. */ export function groupColumnOptions( options: readonly EntityColumnOption[], groups: readonly EntityColumnGroup[], search: string ) { const matchesSearch = createTextMatcher(search); const uniqueOptions = new Map(); const uniqueGroups = new Map(); for (const group of groups) { if (!uniqueGroups.has(group.id)) { uniqueGroups.set(group.id, group); } } for (const option of options) { if (!uniqueOptions.has(option.id)) { uniqueOptions.set(option.id, option); } } const matches = [...uniqueOptions.values()].filter((option) => [option.label, ...(option.keywords ?? [])].some(matchesSearch) ); const grouped = [...uniqueGroups.values()].map((group) => ({ group, options: matches.filter((option) => option.groupId === group.id), })); const ungrouped = matches.filter( (option) => !(option.groupId && uniqueGroups.has(option.groupId)) ); return [ ...grouped.filter((group) => group.options.length > 0), ...(ungrouped.length ? [{ group: undefined, options: ungrouped }] : []), ]; }