/** * Time range + period helpers. Mirrors the Shopify embed implementation so * Hourly/Daily/Monthly behave identically across surfaces. * * (c) 2026 TWWIM UG. All rights reserved. (www.twwim.com) */ import type { IntelligenceGranularity } from '@/infrastructure/http/api/intelligence'; export type TimeRange = 'hourly' | 'daily' | 'monthly'; export const TIME_RANGES: ReadonlyArray = ['hourly', 'daily', 'monthly']; export interface ResolvedRange { range: TimeRange; startDate: Date; endDate: Date; granularity: IntelligenceGranularity; } export function resolveRange(input: string | null | undefined): ResolvedRange { const range: TimeRange = (TIME_RANGES as readonly string[]).includes(input ?? '') ? (input as TimeRange) : 'daily'; const endDate = new Date(); const startDate = new Date(endDate); switch (range) { case 'hourly': startDate.setUTCHours(startDate.getUTCHours() - 24); return { range, startDate, endDate, granularity: 'hour' }; case 'daily': startDate.setUTCDate(startDate.getUTCDate() - 30); return { range, startDate, endDate, granularity: 'day' }; case 'monthly': startDate.setUTCMonth(startDate.getUTCMonth() - 12); return { range, startDate, endDate, granularity: 'month' }; } } export interface PeriodOption { key: string; start: Date; end: Date; label: string; longLabel: string; } export function generatePeriods(range: TimeRange, now: Date = new Date()): PeriodOption[] { const out: PeriodOption[] = []; if (range === 'hourly') { for (let i = 0; i < 24; i++) { const start = new Date(now); start.setUTCMinutes(0, 0, 0); start.setUTCHours(start.getUTCHours() - i); const end = new Date(start); end.setUTCHours(end.getUTCHours() + 1); out.push({ key: start.toISOString(), start, end, label: start.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false }), longLabel: start.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }), }); } } else if (range === 'daily') { for (let i = 0; i < 30; i++) { const start = new Date(now); start.setUTCHours(0, 0, 0, 0); start.setUTCDate(start.getUTCDate() - i); const end = new Date(start); end.setUTCDate(end.getUTCDate() + 1); out.push({ key: start.toISOString().substring(0, 10), start, end, label: String(start.getUTCDate()), longLabel: start.toLocaleDateString(undefined, { dateStyle: 'long' }), }); } } else if (range === 'monthly') { const year = now.getUTCFullYear(); for (let m = 11; m >= 0; m--) { const start = new Date(Date.UTC(year, m, 1)); const end = new Date(Date.UTC(year, m + 1, 1)); out.push({ key: `${year}-${String(m + 1).padStart(2, '0')}`, start, end, label: start.toLocaleDateString(undefined, { month: 'short' }), longLabel: start.toLocaleDateString(undefined, { month: 'long', year: 'numeric' }), }); } } return out; } export function resolvePeriod(range: TimeRange, periodKey: string | null | undefined): PeriodOption { const periods = generatePeriods(range); const match = periodKey ? periods.find((p) => p.key === periodKey) : null; return match ?? periods[0]; } export type KpiWindowScope = 'today' | 'week' | 'month'; export interface KpiWindow { scope: KpiWindowScope; startDate: Date; endDate: Date } export function kpiWindow(range: TimeRange, now: Date = new Date()): KpiWindow { const endDate = new Date(); const startDate = new Date(now); if (range === 'hourly') { startDate.setUTCHours(0, 0, 0, 0); return { scope: 'today', startDate, endDate }; } if (range === 'daily') { const dow = startDate.getUTCDay(); const daysFromMonday = (dow + 6) % 7; startDate.setUTCDate(startDate.getUTCDate() - daysFromMonday); startDate.setUTCHours(0, 0, 0, 0); return { scope: 'week', startDate, endDate }; } startDate.setUTCDate(1); startDate.setUTCHours(0, 0, 0, 0); return { scope: 'month', startDate, endDate }; } export const RANGE_LABELS: Record = { hourly: { en: 'Hourly (24h)', de: 'Stundlich (24 Std.)' }, daily: { en: 'Daily (30 days)', de: 'Taglich (30 Tage)' }, monthly: { en: 'Monthly (this year)', de: 'Monatlich (dieses Jahr)' }, };