feat(transactions): the period as a local ledger with Excel-style filters
The list loads the whole period (or twelve months with Y) and filters it in the browser: a text line over descriptions, notes, accounts and amounts, dropdowns with counts per type, category, budget, account, currency, tag and subscription, group-by with subtotals per currency, pinned filters on 1 to 9. Possible duplicates show with a delete for the newer one, one account adds the balance column, s s sets a subscription and s . repeats the last mass action.
This commit is contained in:
1 parent
01c53d982d
commit
153a8cb191
9 files changed
+1693
-131
No files matched your search
@@ -5,7 +5,6 @@
|
||||
import {
|
||||
type Group,
|
||||
type Signed,
|
||||
signedAmount,
|
||||
sumByCurrency,
|
||||
} from "#lib/transactions/split.js";
|
||||
|
||||
@@ -16,9 +15,10 @@
|
||||
["transfer", "Transferred"],
|
||||
] as const;
|
||||
|
||||
interface Entry extends Signed {
|
||||
interface Entry {
|
||||
href?: string;
|
||||
name: string;
|
||||
sums: Signed[];
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -36,25 +36,29 @@
|
||||
})).filter((row) => row.sums.length > 0)
|
||||
);
|
||||
|
||||
const size = (entry: Entry) =>
|
||||
Math.max(...entry.sums.map((sum) => Math.abs(sum.amount)));
|
||||
|
||||
function aggregate(
|
||||
list: Split[],
|
||||
pick: (split: Split) => [string, string | undefined]
|
||||
): { entries: Entry[]; more: number } {
|
||||
const map = new Map<string, Entry>();
|
||||
const map = new Map<string, { href?: string; splits: Split[] }>();
|
||||
for (const split of list) {
|
||||
const [name, href] = pick(split);
|
||||
const money = signedAmount(split);
|
||||
const key = `${name}|${money.code}`;
|
||||
const entry = map.get(key);
|
||||
if (entry) {
|
||||
entry.amount += money.amount;
|
||||
} else {
|
||||
map.set(key, { ...money, href, name });
|
||||
}
|
||||
const entry = map.get(name) ?? { href, splits: [] };
|
||||
entry.splits.push(split);
|
||||
map.set(name, entry);
|
||||
}
|
||||
const sorted = [...map.values()].sort(
|
||||
(a, b) => Math.abs(b.amount) - Math.abs(a.amount)
|
||||
);
|
||||
const sorted = [...map]
|
||||
.map(([name, entry]) => ({
|
||||
href: entry.href,
|
||||
name,
|
||||
sums: sumByCurrency(entry.splits).sort(
|
||||
(a, b) => Math.abs(b.amount) - Math.abs(a.amount)
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => size(b) - size(a));
|
||||
return { entries: sorted.slice(0, TOP), more: sorted.length - TOP };
|
||||
}
|
||||
|
||||
@@ -127,8 +131,10 @@
|
||||
{#each blocks as block (block.title)}
|
||||
<Box body="py-1" class="max-md:hidden" title={block.title}>
|
||||
<ul>
|
||||
{#each block.entries as entry (`${entry.name}|${entry.code}`)}
|
||||
<li class="flex h-6 items-center justify-between gap-3 px-3">
|
||||
{#each block.entries as entry (entry.name)}
|
||||
<li
|
||||
class="flex min-h-6 items-start justify-between gap-3 px-3 py-0.5"
|
||||
>
|
||||
{#if entry.href}
|
||||
<a class="truncate hover:underline" href={entry.href}
|
||||
>{entry.name}</a
|
||||
@@ -136,7 +142,11 @@
|
||||
{:else}
|
||||
<span class="truncate text-cladd-fg-soft">{entry.name}</span>
|
||||
{/if}
|
||||
{@render amount(entry)}
|
||||
<span class="flex flex-col items-end">
|
||||
{#each entry.sums as sum (sum.code)}
|
||||
{@render amount(sum)}
|
||||
{/each}
|
||||
</span>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="px-3 py-1 text-cladd-fg-soft">
|
||||
|
||||
Reference in new issue
Block a user