import { ReportPeriod, ReportBucket, GroupKey, } from "../features/report"; // ─── Types ──────────────────────────────────────────────────── export type PeriodKey = "weekly" | "monthly" | "yearly" | "fyly" | "full"; export type DecoratedPeriod = ReportPeriod & { id: string; label: string; }; // ─── Period helpers ─────────────────────────────────────────── const PREFIX_TO_KEY: Record = { W: "weekly", M: "monthly", Y: "yearly", FY: "fyly", FULL: "full", }; /** * Derive the period key from a decorated-period id. * E.g. `"W:2026-04-28_2026-05-04"` → `"weekly"` */ export function periodIdToKey(periodId: string): PeriodKey { const prefix = periodId.split(":")[0]; return PREFIX_TO_KEY[prefix] ?? "full"; } // ─── Metric helpers ─────────────────────────────────────────── export function getAmount( period: ReportPeriod, mode: "expense" | "income" ): number { return mode === "expense" ? period.expenses.sum : period.incomes.sum; } function mergeMetric(a: ReportPeriod["expenses"], b: ReportPeriod["expenses"]) { const sum = a.sum + b.sum; const count = a.count + b.count; return { ...a, sum, count, average: count > 0 ? sum / count : 0, transactions: a.transactions || b.transactions ? [...(a.transactions || []), ...(b.transactions || [])] : undefined, }; } /** * Merge periods with the same id across all buckets, summing * their metrics and concatenating transactions. * * Returns sorted by start date ascending. */ export function mergeBucketPeriods( buckets: ReportBucket[], key: PeriodKey ): DecoratedPeriod[] { const map = new Map(); for (const bucket of buckets) { const periods = (bucket.periods[key] || []) as DecoratedPeriod[]; for (const p of periods) { const existing = map.get(p.id); if (!existing) { map.set(p.id, { ...p, expenses: { ...p.expenses }, incomes: { ...p.incomes }, }); } else { map.set(p.id, { ...existing, expenses: mergeMetric(existing.expenses, p.expenses), incomes: mergeMetric(existing.incomes, p.incomes), }); } } } return Array.from(map.values()).sort( (a, b) => new Date(a.start).getTime() - new Date(b.start).getTime() ); } // ─── Formatting ─────────────────────────────────────────────── export const formatCurrency = (val: number) => { const absVal = Math.abs(val); if (absVal >= 100000) { return `₹ ${(val / 100000).toFixed(2)}L`; } if (absVal >= 1000) { return `₹ ${(val / 1000).toFixed(2)}k`; } return `₹ ${val.toFixed(2)}`; }; export const getPercentage = (progressAmount: number, totalAmount: number) => { if (!totalAmount) return 0; return Math.min(100, Math.max(0, (progressAmount / totalAmount) * 100)); }; // ─── Group filtering ────────────────────────────────────────── /** * Check if a bucket's group_key matches the selected GroupKey. * Every dimension present in `selected` must exist in the bucket * and contain all the selected values. */ export function matchesGroupKey( bucket: ReportBucket, selected: GroupKey ): boolean { for (const [dim, values] of Object.entries(selected)) { const bucketValues = bucket.group_key[dim as keyof GroupKey]; if (!bucketValues) return false; if (!(values as string[]).every((v) => bucketValues.includes(v))) return false; } return true; } /** * Return only buckets matching the selected group key, * or all buckets if no selection. */ export function filterBuckets( buckets: ReportBucket[], selectedGroupKey: GroupKey | null ): ReportBucket[] { if (!selectedGroupKey) return buckets; return buckets.filter((b) => matchesGroupKey(b, selectedGroupKey)); }