167 lines
4.6 KiB
TypeScript
167 lines
4.6 KiB
TypeScript
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<string, unknown>,
|
|
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<string, unknown>[],
|
|
groupBy: string | undefined,
|
|
sortBy: string | undefined,
|
|
sortDir: "asc" | "desc",
|
|
columns: ColumnDef[],
|
|
): Record<string, unknown>[] {
|
|
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<string, unknown>, columns: string[]): Record<string, unknown> {
|
|
return Object.fromEntries(columns.map((column) => [column, row[column]]));
|
|
}
|
|
|
|
export function buildReportGroups(
|
|
rows: Record<string, unknown>[],
|
|
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);
|
|
}
|