diff --git a/src/common/components/TransactionList.tsx b/src/common/components/TransactionList.tsx
index db26e34..80c84b1 100644
--- a/src/common/components/TransactionList.tsx
+++ b/src/common/components/TransactionList.tsx
@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import {
Box,
Typography,
- Paper,
Accordion,
AccordionSummary,
AccordionDetails,
@@ -18,6 +17,7 @@ import type { ExpenseItem, TxnFieldConfigs } from "../types";
import { computeTxnMetrics, groupByDate, groupByPeriod } from "../utils/transactions";
import type { PeriodGranularity } from "../utils/transactions";
import { TransactionRow } from "./TransactionRow";
+import { StatCard } from "./StatCard";
interface TransactionListProps {
items: ExpenseItem[];
@@ -30,24 +30,21 @@ function GroupMetrics({ items, currency }: { items: ExpenseItem[]; currency: str
const m = computeTxnMetrics(items);
const rows: { label: string; value: string }[] = [
{ label: "Sum", value: formatCurrency(m.sum, currency) },
- { label: "Count", value: m.count.toLocaleString("en-IN") },
{ label: "Avg", value: m.avg == null ? "—" : formatCurrency(m.avg, currency) },
{ label: "Min", value: m.min == null ? "—" : formatCurrency(m.min, currency) },
{ label: "Max", value: m.max == null ? "—" : formatCurrency(m.max, currency) },
- { label: "First", value: m.firstDate ?? "—" },
- { label: "Last", value: m.lastDate ?? "—" },
+ {
+ label: "Cadence",
+ value:
+ m.cadenceDays == null
+ ? "—"
+ : `${Number.isInteger(m.cadenceDays) ? m.cadenceDays : m.cadenceDays.toFixed(2)} days`,
+ },
];
return (
-
+
{rows.map((row) => (
-
-
- {row.label}
-
-
- {row.value}
-
-
+
))}
);
@@ -190,9 +187,7 @@ export function TransactionList({ items, fields, granularity = "monthly", showMe
{formatCurrency(group.income, group.currency)}
-
- {showMetrics && }
-
+ {showMetrics && }
diff --git a/src/common/utils/transactions.ts b/src/common/utils/transactions.ts
index c72e13b..dd9996e 100644
--- a/src/common/utils/transactions.ts
+++ b/src/common/utils/transactions.ts
@@ -114,27 +114,36 @@ export interface TxnMetrics {
avg: number | null;
min: number | null;
max: number | null;
- firstDate: string | null;
- lastDate: string | null;
+ cadenceDays: number | null;
}
/** Mirrors backend ReportMetrics.compute_metrics over a list of transactions. */
export function computeTxnMetrics(items: ExpenseItem[]): TxnMetrics {
const amounts = items.filter((it) => typeof it.amount === "number").map((it) => it.amount as number);
+ const empty = { sum: 0, count: 0, avg: null, min: null, max: null, cadenceDays: null };
if (amounts.length === 0) {
- return { sum: 0, count: 0, avg: null, min: null, max: null, firstDate: null, lastDate: null };
+ return empty;
}
- const sorted = [...items].sort(
- (a, b) => parseOccurredAt(a.occurred_at).getTime() - parseOccurredAt(b.occurred_at).getTime(),
- );
+ const dates = items
+ .map((it) => it.occurred_at)
+ .filter((d): d is string => !!d)
+ .map((d) => parseOccurredAt(d).getTime())
+ .sort((a, b) => a - b);
const sum = amounts.reduce((s, a) => s + a, 0);
+ let cadenceDays: number | null = null;
+ if (dates.length >= 2) {
+ const gaps: number[] = [];
+ for (let i = 0; i < dates.length - 1; i += 1) {
+ gaps.push((dates[i + 1] - dates[i]) / 86400000);
+ }
+ cadenceDays = Math.round((gaps.reduce((s, g) => s + g, 0) / gaps.length) * 100) / 100;
+ }
return {
sum,
count: amounts.length,
avg: sum / amounts.length,
min: Math.min(...amounts),
max: Math.max(...amounts),
- firstDate: sorted[0]?.occurred_at ?? null,
- lastDate: sorted[sorted.length - 1]?.occurred_at ?? null,
+ cadenceDays,
};
}
\ No newline at end of file