feat(dashboard,accounts,charts): v1 dashboard redrawn, account lists and pages, svg chart kit

This commit is contained in:
hh committed 2026-10-16 03:50:02 +00:00
1 parent 62cd17ecde
commit 9e89829bae
36 files changed
+5678 -16

No files matched your search

@@ -0,0 +1,103 @@
<script lang="ts">
import { createQuery, keepPreviousData } from "@tanstack/svelte-query";
import { showUrl } from "#lib/components/accounts/links.js";
import Box from "#lib/components/Box.svelte";
import BarChart from "#lib/components/charts/BarChart.svelte";
import { tones } from "#lib/components/charts/chart.js";
import { toNumber } from "#lib/format.js";
import { period } from "#lib/period.svelte.js";
import { accountsQuery, byCurrency, money } from "./data.js";
const liabilities = createQuery(() => ({
...accountsQuery("liabilities", period.start, period.end),
placeholderData: keepPreviousData,
}));
const groups = $derived(
byCurrency(
(liabilities.data ?? []).filter(
(a) =>
a.attributes.liability_type === "debt" &&
toNumber(a.attributes.debt_amount) !== 0
),
(a) => a.attributes.currency_code ?? ""
).map(([code, list]) => {
const first = list[0].attributes;
const format = money({
code,
decimals: first.currency_decimal_places ?? 2,
symbol: first.currency_symbol ?? code,
});
const bars = list
.map((a) => {
const due = Math.abs(toNumber(a.attributes.debt_amount));
const value =
a.attributes.liability_direction === "credit" ? due : -due;
return {
color: value > 0 ? tones.income : tones.expense,
href: showUrl(a.id),
key: a.id,
label: a.attributes.name,
value,
};
})
.sort((x, y) => y.value - x.value);
const owed = bars
.filter((b) => b.value > 0)
.reduce((s, b) => s + b.value, 0);
const owe = bars
.filter((b) => b.value < 0)
.reduce((s, b) => s - b.value, 0);
return { bars, code, format, owe, owed };
})
);
</script>
{#if groups.length > 0}
<Box href="/accounts/liabilities" title="Debts with friends">
<div
class="flex flex-col gap-4 transition-opacity {liabilities.isPlaceholderData
? "opacity-60"
: ""}"
>
{#each groups as group (group.code)}
<div class="flex flex-col gap-1.5">
<div class="flex items-baseline gap-3 text-[12px]">
<span
class="font-semibold text-[11px] text-cladd-fg-soft tracking-wide"
>{group.code}</span
>
{#if group.owed > 0}
<span class="text-cladd-fg-soft"
>Owed to me
<span class="num text-income"
>{group.format(group.owed)}</span
></span
>
{/if}
{#if group.owe > 0}
<span class="text-cladd-fg-soft"
>I owe
<span class="num text-expense"
>{group.format(group.owe)}</span
></span
>
{/if}
</div>
<BarChart
bars={group.bars}
format={group.format}
label="Debts with friends in {group.code}"
labelWidth="minmax(5rem, 9rem)"
orientation="horizontal"
/>
</div>
{/each}
</div>
{#snippet footer()}
<a class="text-brand hover:underline" href="/accounts/liabilities"
>See your liabilities</a
>
{/snippet}
</Box>
{/if}