assafvayner HF Staff commited on
Commit
3327d9d
·
1 Parent(s): edcd76e

feat(ui): add balances page with simplified debts and settle-up links

Browse files
src/routes/balances/+page.server.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { computeNets, simplifyDebts } from '$lib/domain/balances';
2
+ import { centsToInput } from '$lib/domain/money';
3
+ import type { PageServerLoad } from './$types';
4
+
5
+ export const load: PageServerLoad = ({ locals }) => {
6
+ const { ledger, users } = locals.app;
7
+ const me = locals.user!;
8
+ const nets = computeNets(ledger.listExpenses(), ledger.listSettlements());
9
+ const name = (id: string) => users.byId(id)?.displayName ?? 'Unknown';
10
+
11
+ const rows = users
12
+ .members()
13
+ .filter((u) => u.active || (nets.get(u.id) ?? 0) !== 0)
14
+ .map((u) => ({ id: u.id, name: u.displayName, active: u.active, net: nets.get(u.id) ?? 0 }))
15
+ .sort((a, b) => b.net - a.net || a.name.localeCompare(b.name));
16
+
17
+ const debts = simplifyDebts(nets).map((d) => ({
18
+ ...d,
19
+ fromName: name(d.from),
20
+ toName: name(d.to),
21
+ settleHref: `/settlements/new?from=${encodeURIComponent(d.from)}&to=${encodeURIComponent(d.to)}&amount=${centsToInput(d.cents)}`
22
+ }));
23
+
24
+ return { rows, debts, myNet: nets.get(me.id) ?? 0 };
25
+ };
src/routes/balances/+page.svelte ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import MoneyText from '$lib/components/MoneyText.svelte';
3
+ import type { PageProps } from './$types';
4
+
5
+ let { data }: PageProps = $props();
6
+ </script>
7
+
8
+ <main class="space-y-6 p-4">
9
+ <h1 class="text-2xl font-bold">Balances</h1>
10
+
11
+ {#if data.user?.role === 'member'}
12
+ <section class="rounded-xl bg-white p-4 shadow-sm">
13
+ {#if data.myNet > 0}
14
+ <p class="text-gray-600">You are owed</p>
15
+ {:else if data.myNet < 0}
16
+ <p class="text-gray-600">You owe</p>
17
+ {:else}
18
+ <p class="text-gray-600">You're all settled up</p>
19
+ {/if}
20
+ <MoneyText
21
+ cents={Math.abs(data.myNet)}
22
+ currency={data.currency}
23
+ class="text-3xl font-bold {data.myNet > 0
24
+ ? 'text-green-700'
25
+ : data.myNet < 0
26
+ ? 'text-red-700'
27
+ : ''}"
28
+ />
29
+ </section>
30
+ {/if}
31
+
32
+ <section>
33
+ <h2 class="mb-2 text-sm font-semibold tracking-wide text-gray-500 uppercase">
34
+ Suggested payments
35
+ </h2>
36
+ {#if data.debts.length === 0}
37
+ <p class="rounded-xl bg-white p-4 text-gray-500 shadow-sm">Nobody owes anything. 🎉</p>
38
+ {:else}
39
+ <ul class="divide-y divide-gray-200 rounded-xl bg-white shadow-sm">
40
+ {#each data.debts as d (d.from + d.to)}
41
+ <li class="flex items-center gap-3 px-4 py-3">
42
+ <div class="flex-1">
43
+ <span class="font-medium">{d.fromName}</span>
44
+ owes
45
+ <span class="font-medium">{d.toName}</span>
46
+ </div>
47
+ <MoneyText cents={d.cents} currency={data.currency} class="font-medium" />
48
+ <a
49
+ href={d.settleHref}
50
+ class="rounded-lg bg-green-700 px-3 py-1 text-sm font-semibold text-white">Settle up</a
51
+ >
52
+ </li>
53
+ {/each}
54
+ </ul>
55
+ {/if}
56
+ </section>
57
+
58
+ <section>
59
+ <h2 class="mb-2 text-sm font-semibold tracking-wide text-gray-500 uppercase">Everyone</h2>
60
+ <ul class="divide-y divide-gray-200 rounded-xl bg-white shadow-sm">
61
+ {#each data.rows as r (r.id)}
62
+ <li class="flex justify-between px-4 py-3 {r.active ? '' : 'text-gray-400'}">
63
+ <span
64
+ >{r.name}{r.id === data.user?.id ? ' (you)' : ''}{r.active ? '' : ' · inactive'}</span
65
+ >
66
+ <MoneyText cents={r.net} currency={data.currency} signed />
67
+ </li>
68
+ {/each}
69
+ </ul>
70
+ </section>
71
+ </main>