feat(admin): Rapporten screen with filters, totals, breakdowns + CSV export

This commit is contained in:
Bas van Rossem
2026-06-24 16:40:25 +02:00
parent 8ad2e69ec3
commit 8d75be0462
7 changed files with 465 additions and 2 deletions

View File

@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest';
import { dayStartISO, dayEndISO, thisWeek, thisMonth } from './date-range';
describe('date-range helpers', () => {
it('dayStartISO/dayEndISO bound a local day', () => {
const start = new Date(dayStartISO('2026-06-17'));
const end = new Date(dayEndISO('2026-06-17'));
expect(start.getHours()).toBe(0);
expect(start.getMinutes()).toBe(0);
expect(end.getHours()).toBe(23);
expect(end.getMinutes()).toBe(59);
expect(end.getTime()).toBeGreaterThan(start.getTime());
});
it('thisWeek returns Monday..today (from/to are YYYY-MM-DD)', () => {
const ref = new Date('2026-06-17T12:00:00'); // a Wednesday
const { from, to } = thisWeek(ref);
expect(from).toBe('2026-06-15'); // Monday
expect(to).toBe('2026-06-17');
});
it('thisMonth returns the 1st..today', () => {
const ref = new Date('2026-06-17T12:00:00');
const { from, to } = thisMonth(ref);
expect(from).toBe('2026-06-01');
expect(to).toBe('2026-06-17');
});
});

View File

@@ -0,0 +1,40 @@
// Helpers for the report filter bar. Dates are 'YYYY-MM-DD' (what <input type="date"> uses);
// the *ISO instants* sent to the API are derived in the admin's local timezone so a picked
// day means that whole local day.
function pad(n: number): string {
return String(n).padStart(2, '0');
}
export function toDateStr(d: Date): string {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
// Start of the given local day, as an ISO instant.
export function dayStartISO(dateStr: string): string {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d, 0, 0, 0, 0).toISOString();
}
// End of the given local day, as an ISO instant.
export function dayEndISO(dateStr: string): string {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d, 23, 59, 59, 999).toISOString();
}
export function thisWeek(ref: Date): { from: string; to: string } {
const day = ref.getDay(); // 0=Sun..6=Sat
const diffToMonday = (day + 6) % 7;
const monday = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate() - diffToMonday);
return { from: toDateStr(monday), to: toDateStr(ref) };
}
export function thisMonth(ref: Date): { from: string; to: string } {
const first = new Date(ref.getFullYear(), ref.getMonth(), 1);
return { from: toDateStr(first), to: toDateStr(ref) };
}
// 'Alles' — a wide window from epoch-ish to today.
export function allTime(ref: Date): { from: string; to: string } {
return { from: '2000-01-01', to: toDateStr(ref) };
}