116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import type { Column, TDisplayFunction } from './types.js';
|
|
|
|
export function computeColumnsFromDisplayFunction<T>(
|
|
displayFunction: TDisplayFunction<T>,
|
|
data: T[]
|
|
): Column<T>[] {
|
|
if (!data || data.length === 0) return [];
|
|
const firstTransformedItem = displayFunction(data[0]);
|
|
const keys: string[] = Object.keys(firstTransformedItem);
|
|
return keys.map((key) => ({
|
|
key,
|
|
header: key,
|
|
value: (row: T) => displayFunction(row)[key],
|
|
}));
|
|
}
|
|
|
|
export function computeEffectiveColumns<T>(
|
|
columns: Column<T>[] | undefined,
|
|
augmentFromDisplayFunction: boolean,
|
|
displayFunction: TDisplayFunction<T>,
|
|
data: T[]
|
|
): Column<T>[] {
|
|
const base = (columns || []).slice();
|
|
if (!augmentFromDisplayFunction) return base;
|
|
const fromDisplay = computeColumnsFromDisplayFunction(displayFunction, data);
|
|
const existingKeys = new Set(base.map((c) => String(c.key)));
|
|
for (const col of fromDisplay) {
|
|
if (!existingKeys.has(String(col.key))) {
|
|
base.push(col);
|
|
}
|
|
}
|
|
return base;
|
|
}
|
|
|
|
export function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDisplayFunction<T>): any {
|
|
return col.value ? col.value(row) : (row as any)[col.key as any];
|
|
}
|
|
|
|
export function getViewData<T>(
|
|
data: T[],
|
|
effectiveColumns: Column<T>[],
|
|
sortKey?: string,
|
|
sortDir?: 'asc' | 'desc' | null,
|
|
filterText?: string,
|
|
columnFilters?: Record<string, string>,
|
|
filterMode: 'table' | 'data' = 'table',
|
|
lucenePredicate?: (row: T) => boolean
|
|
): T[] {
|
|
let arr = data.slice();
|
|
const ft = (filterText || '').trim().toLowerCase();
|
|
const cf = columnFilters || {};
|
|
const cfKeys = Object.keys(cf).filter((k) => (cf[k] ?? '').trim().length > 0);
|
|
if (ft || cfKeys.length > 0) {
|
|
arr = arr.filter((row) => {
|
|
// column filters (AND across columns)
|
|
for (const k of cfKeys) {
|
|
if (filterMode === 'data') {
|
|
// raw object check for that key
|
|
const val = (row as any)[k];
|
|
const s = String(val ?? '').toLowerCase();
|
|
const needle = String(cf[k]).toLowerCase();
|
|
if (!s.includes(needle)) return false;
|
|
} else {
|
|
const col = effectiveColumns.find((c) => String(c.key) === k);
|
|
if (!col || col.hidden || col.filterable === false) continue;
|
|
const val = getCellValue(row, col);
|
|
const s = String(val ?? '').toLowerCase();
|
|
const needle = String(cf[k]).toLowerCase();
|
|
if (!s.includes(needle)) return false;
|
|
}
|
|
}
|
|
// global filter (OR across visible columns) or lucene predicate
|
|
if (ft) {
|
|
if (lucenePredicate) {
|
|
if (!lucenePredicate(row)) return false;
|
|
return true;
|
|
}
|
|
let any = false;
|
|
if (filterMode === 'data') {
|
|
for (const val of Object.values(row as any)) {
|
|
const s = String(val ?? '').toLowerCase();
|
|
if (s.includes(ft)) { any = true; break; }
|
|
}
|
|
} else {
|
|
for (const col of effectiveColumns) {
|
|
if (col.hidden) continue;
|
|
const val = getCellValue(row, col);
|
|
const s = String(val ?? '').toLowerCase();
|
|
if (s.includes(ft)) { any = true; break; }
|
|
}
|
|
}
|
|
if (!any) return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
if (!sortKey || !sortDir) return arr;
|
|
const col = effectiveColumns.find((c) => String(c.key) === sortKey);
|
|
if (!col) return arr;
|
|
const dir = sortDir === 'asc' ? 1 : -1;
|
|
arr.sort((a, b) => {
|
|
const va = getCellValue(a, col);
|
|
const vb = getCellValue(b, col);
|
|
if (va == null && vb == null) return 0;
|
|
if (va == null) return -1 * dir;
|
|
if (vb == null) return 1 * dir;
|
|
if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
|
|
const sa = String(va).toLowerCase();
|
|
const sb = String(vb).toLowerCase();
|
|
if (sa < sb) return -1 * dir;
|
|
if (sa > sb) return 1 * dir;
|
|
return 0;
|
|
});
|
|
return arr;
|
|
}
|