import type { FilterInput, ReportGroupSummary } from "./report-query-config.js"; import type { ColumnDef } from "./report-columns.js"; function parseFilterValue(def: ColumnDef | undefined, value: string): unknown { if (!def) { return value; } if (def.dataType === "number") { const parsed = Number(value); return Number.isNaN(parsed) ? null : parsed; } if (def.dataType === "boolean") { return value === "true"; } if (def.dataType === "date") { const parsed = new Date(value); return Number.isNaN(parsed.getTime()) ? null : parsed.getTime(); } return value; } export function matchesInMemoryFilter( row: Record, filter: FilterInput, columns: ColumnDef[], ): boolean { const def = columns.find((column) => column.key === filter.field); if (!def) { return true; } const rowValueRaw = row[filter.field]; const rowValue = def.dataType === "date" && typeof rowValueRaw === "string" ? new Date(rowValueRaw).getTime() : rowValueRaw; const parsedFilterValue = parseFilterValue(def, filter.value); if (parsedFilterValue === null) { return false; } switch (filter.op) { case "eq": return rowValue === parsedFilterValue; case "neq": return rowValue !== parsedFilterValue; case "gt": return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue > parsedFilterValue; case "lt": return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue < parsedFilterValue; case "gte": return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue >= parsedFilterValue; case "lte": return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue <= parsedFilterValue; case "contains": return typeof rowValue === "string" && rowValue.toLowerCase().includes(filter.value.toLowerCase()); case "in": return filter.value.split(",").map((value) => value.trim()).includes(String(rowValue ?? "")); default: return true; } } export function sortInMemoryRows( rows: Record[], groupBy: string | undefined, sortBy: string | undefined, sortDir: "asc" | "desc", columns: ColumnDef[], ): Record[] { if (!groupBy && !sortBy) { return rows; } return [...rows].sort((left, right) => { if (groupBy) { const groupDef = columns.find((column) => column.key === groupBy); const groupComparison = compareRowValues(left[groupBy], right[groupBy], groupDef, "asc"); if (groupComparison !== 0) { return groupComparison; } } if (!sortBy) { return 0; } const sortDef = columns.find((column) => column.key === sortBy); return compareRowValues(left[sortBy], right[sortBy], sortDef, sortDir); }); } function compareRowValues( leftValue: unknown, rightValue: unknown, def: ColumnDef | undefined, sortDir: "asc" | "desc", ): number { if (leftValue == null && rightValue == null) { return 0; } if (leftValue == null) { return 1; } if (rightValue == null) { return -1; } const direction = sortDir === "asc" ? 1 : -1; if (def?.dataType === "number") { return direction * (Number(leftValue) - Number(rightValue)); } if (def?.dataType === "boolean") { return direction * (Number(Boolean(leftValue)) - Number(Boolean(rightValue))); } if (def?.dataType === "date") { return direction * (new Date(String(leftValue)).getTime() - new Date(String(rightValue)).getTime()); } return direction * String(leftValue).localeCompare(String(rightValue), "de"); } export function pickColumns(row: Record, columns: string[]): Record { return Object.fromEntries(columns.map((column) => [column, row[column]])); } export function buildReportGroups( rows: Record[], groupBy: string | undefined, ): ReportGroupSummary[] { if (!groupBy) { return []; } const groups: ReportGroupSummary[] = []; let currentKey: string | null = null; rows.forEach((row, index) => { const rawValue = row[groupBy]; const label = formatGroupValue(rawValue); const key = `${groupBy}:${label}`; if (key !== currentKey) { groups.push({ key, label, rowCount: 1, startIndex: index, }); currentKey = key; return; } groups[groups.length - 1]!.rowCount += 1; }); return groups; } function formatGroupValue(value: unknown): string { if (value === null || value === undefined || value === "") { return "No value"; } if (value instanceof Date) { return value.toISOString(); } return String(value); }