67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
|
|
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;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Normaliza tildes sin equiparar ñ y n, incluidas sus representaciones Unicode descompuestas. */
|
||
|
|
export function normalizeColumnSearch(value: string): string {
|
||
|
|
return value
|
||
|
|
.trim()
|
||
|
|
.toLowerCase()
|
||
|
|
.normalize("NFD")
|
||
|
|
.replace(/n\u0303/g, "ñ")
|
||
|
|
.replace(/\p{M}/gu, "");
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 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 term = normalizeColumnSearch(search);
|
||
|
|
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) =>
|
||
|
|
[option.label, ...(option.keywords ?? [])].some((value) =>
|
||
|
|
normalizeColumnSearch(value).includes(term)
|
||
|
|
)
|
||
|
|
);
|
||
|
|
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 }] : []),
|
||
|
|
];
|
||
|
|
}
|