Files
anouma/lib/analytics/range.ts
T

72 lines
3.0 KiB
TypeScript

import type { DashboardRange } from "@/lib/analytics/dashboardQueries";
export type RangeKey = "today" | "7d" | "30d" | "90d" | "custom";
export const RANGE_OPTIONS: { key: RangeKey; label: string; days: number | "today" }[] = [
{ key: "today", label: "Heute", days: "today" },
{ key: "7d", label: "7 Tage", days: 7 },
{ key: "30d", label: "30 Tage", days: 30 },
{ key: "90d", label: "90 Tage", days: 90 },
];
const DAY_MS = 24 * 60 * 60 * 1000;
function startOfUTCDay(date: Date): Date {
return new Date(`${date.toISOString().slice(0, 10)}T00:00:00.000Z`);
}
function endOfUTCDay(date: Date): Date {
return new Date(`${date.toISOString().slice(0, 10)}T23:59:59.999Z`);
}
function parseDateParam(value: string | null): Date | null {
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
const date = new Date(`${value}T00:00:00.000Z`);
return Number.isNaN(date.getTime()) ? null : date;
}
/**
* Resolves the dashboard's period filter (?range=&from=&to=) into an actual
* date range, clamped to `maxDaysBack` (the configured raw-event retention -
* see globals/AnalyticsSettings.ts) so the UI never claims to show data that
* has already been purged. Also computes the matching "previous period" for
* comparison (same length, immediately preceding).
*/
export function resolveRange(
searchParams: URLSearchParams,
maxDaysBack: number,
): { key: RangeKey; range: DashboardRange; compareRange: DashboardRange } {
const now = new Date();
const earliestAllowed = startOfUTCDay(new Date(now.getTime() - maxDaysBack * DAY_MS));
const rawKey = searchParams.get("range");
const key: RangeKey = (["today", "7d", "30d", "90d", "custom"] as const).includes(rawKey as RangeKey)
? (rawKey as RangeKey)
: "7d";
if (key === "custom") {
const fromParam = parseDateParam(searchParams.get("from"));
const toParam = parseDateParam(searchParams.get("to"));
let from = fromParam ? startOfUTCDay(fromParam) : startOfUTCDay(new Date(now.getTime() - 7 * DAY_MS));
let to = toParam ? endOfUTCDay(toParam) : now;
if (from < earliestAllowed) from = earliestAllowed;
if (to > now) to = now;
if (from > to) from = to;
const lengthMs = to.getTime() - from.getTime();
const compareRange: DashboardRange = { from: new Date(from.getTime() - lengthMs), to: new Date(from.getTime() - 1) };
return { key, range: { from, to }, compareRange };
}
if (key === "today") {
const from = startOfUTCDay(now);
const yesterday = new Date(from.getTime() - DAY_MS);
return { key, range: { from, to: now }, compareRange: { from: yesterday, to: new Date(from.getTime() - 1) } };
}
const days = RANGE_OPTIONS.find((o) => o.key === key)?.days;
const windowDays = typeof days === "number" ? Math.min(days, maxDaysBack) : 7;
const from = new Date(Math.max(now.getTime() - windowDays * DAY_MS, earliestAllowed.getTime()));
const compareRange: DashboardRange = { from: new Date(from.getTime() - windowDays * DAY_MS), to: new Date(from.getTime() - 1) };
return { key, range: { from, to: now }, compareRange };
}