2026-10-24 23:19:17 +00:00
|
|
|
import { createTextMatcher } from "./text-search";
|
|
|
|
|
|
|
|
|
|
export { normalizeSearchText as normalizeColumnSearch } from "./text-search";
|
|
|
|
|
|
2026-10-22 08:10:34 +00:00
|
|
|
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
|
|
|
|
|
) {
|
2026-10-24 23:19:17 +00:00
|
|
|
const matchesSearch = createTextMatcher(search);
|
2026-10-22 08:10:34 +00:00
|
|
|
const uniqueOptions = new Map<string, EntityColumnOption>();
|
|
|
|
|
const uniqueGroups = new Map<string, EntityColumnGroup>();
|
|
|
|
|
|
|
|
|
|
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) =>
|
2026-10-24 23:19:17 +00:00
|
|
|
[option.label, ...(option.keywords ?? [])].some(matchesSearch)
|
2026-10-22 08:10:34 +00:00
|
|
|
);
|
|
|
|
|
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 }] : []),
|
|
|
|
|
];
|
|
|
|
|
}
|