Files
CapaKraken/packages/api/src/router/report-query-utils.ts
T

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);
}