money: quick-add bar, server-filtered transactions page, CSV import dedupe + Revolut statements
This commit is contained in:
73
src/lib/money-tracker/quick-add.ts
Normal file
73
src/lib/money-tracker/quick-add.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export type SuggestionSource = {
|
||||
description: string;
|
||||
amountCents: number;
|
||||
accountId: number | null;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type Suggestion = {
|
||||
key: string;
|
||||
description: string;
|
||||
/** Most common amount for this description (latest wins ties). */
|
||||
amountCents: number;
|
||||
/** Account used most recently with this description. */
|
||||
accountId: number | null;
|
||||
count: number;
|
||||
lastDate: string;
|
||||
};
|
||||
|
||||
/** Past descriptions ranked by use, each with its usual amount and latest account. */
|
||||
export function descriptionSuggestions(rows: SuggestionSource[], today: string): Suggestion[] {
|
||||
const groups = new Map<
|
||||
string,
|
||||
{
|
||||
description: string;
|
||||
amounts: Map<number, number>;
|
||||
accountId: number | null;
|
||||
count: number;
|
||||
lastDate: string;
|
||||
lastAmount: number;
|
||||
}
|
||||
>();
|
||||
for (const row of rows) {
|
||||
if (row.date > today) continue;
|
||||
const key = row.description.trim().toLowerCase();
|
||||
if (!key) continue;
|
||||
const group = groups.get(key) ?? {
|
||||
description: row.description.trim(),
|
||||
amounts: new Map(),
|
||||
accountId: row.accountId,
|
||||
count: 0,
|
||||
lastDate: '',
|
||||
lastAmount: row.amountCents
|
||||
};
|
||||
group.count++;
|
||||
group.amounts.set(row.amountCents, (group.amounts.get(row.amountCents) ?? 0) + 1);
|
||||
if (row.date >= group.lastDate) {
|
||||
group.lastDate = row.date;
|
||||
group.description = row.description.trim();
|
||||
group.accountId = row.accountId;
|
||||
group.lastAmount = row.amountCents;
|
||||
}
|
||||
groups.set(key, group);
|
||||
}
|
||||
return [...groups.entries()]
|
||||
.map(([key, group]) => {
|
||||
let amountCents = group.lastAmount;
|
||||
let best = group.amounts.get(amountCents) ?? 0;
|
||||
for (const [value, uses] of group.amounts)
|
||||
if (uses > best) {
|
||||
best = uses;
|
||||
amountCents = value;
|
||||
}
|
||||
return {
|
||||
key,
|
||||
description: group.description,
|
||||
amountCents,
|
||||
accountId: group.accountId,
|
||||
count: group.count,
|
||||
lastDate: group.lastDate
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.count - a.count || b.lastDate.localeCompare(a.lastDate));
|
||||
}
|
||||
Reference in New Issue
Block a user