feat(admin): Rapporten screen with filters, totals, breakdowns + CSV export
This commit is contained in:
28
apps/admin/src/lib/date-range.test.ts
Normal file
28
apps/admin/src/lib/date-range.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
40
apps/admin/src/lib/date-range.ts
Normal file
40
apps/admin/src/lib/date-range.ts
Normal 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) };
|
||||
}
|
||||
Reference in New Issue
Block a user