247 lines
8.6 KiB
Svelte
247 lines
8.6 KiB
Svelte
<script lang="ts">
|
|
import ArrowLeft from "@lucide/svelte/icons/arrow-left";
|
|
import ArrowLeftRight from "@lucide/svelte/icons/arrow-left-right";
|
|
import ArrowRight from "@lucide/svelte/icons/arrow-right";
|
|
import Calculator from "@lucide/svelte/icons/calculator";
|
|
import Star from "@lucide/svelte/icons/star";
|
|
import { createQuery } from "@tanstack/svelte-query";
|
|
import type { Account } from "#lib/api/types.js";
|
|
import Box from "#lib/components/Box.svelte";
|
|
import { addDays } from "#lib/dates.js";
|
|
import { formatDate, formatDateTime } from "#lib/format.js";
|
|
import { type AuditColumn, ledger } from "#lib/reports/audit.js";
|
|
import { reportQueries } from "#lib/reports/queries.js";
|
|
import { money, useReport } from "#lib/reports/report.svelte.js";
|
|
|
|
let {
|
|
id,
|
|
account,
|
|
before,
|
|
after,
|
|
columns,
|
|
}: {
|
|
id: string;
|
|
account: Account | undefined;
|
|
before: number | undefined;
|
|
after: number | undefined;
|
|
columns: AuditColumn[];
|
|
} = $props();
|
|
|
|
const report = useReport();
|
|
const p = $derived(report.params);
|
|
const code = $derived(account?.currency_code ?? "");
|
|
const query = createQuery(() => reportQueries.ledger(id, p.start, p.end));
|
|
const rows = $derived(
|
|
query.data && before !== undefined
|
|
? ledger(query.data, id, code, before)
|
|
: undefined
|
|
);
|
|
const show = (key: AuditColumn) => columns.includes(key);
|
|
const ICONS = {
|
|
deposit: ArrowRight,
|
|
"opening balance": Star,
|
|
reconciliation: Calculator,
|
|
transfer: ArrowLeftRight,
|
|
withdrawal: ArrowLeft,
|
|
} as const;
|
|
const META_DATES = [
|
|
"interest_date",
|
|
"book_date",
|
|
"process_date",
|
|
"due_date",
|
|
"payment_date",
|
|
"invoice_date",
|
|
] as const;
|
|
const dateColumns = $derived(META_DATES.filter((key) => show(key)));
|
|
</script>
|
|
|
|
<Box body="p-0" href="/accounts/show/{id}" title={account?.name ?? `#${id}`}>
|
|
{#if !rows}
|
|
<p class="px-3 py-3 text-cladd-fg-soft">Loading</p>
|
|
{:else if rows.length === 0}
|
|
<p class="px-3 py-3">
|
|
No activity was recorded on account
|
|
<a
|
|
class="text-brand dark:text-[var(--ff-series-1)] hover:underline"
|
|
href="/accounts/show/{id}"
|
|
>{account?.name}</a
|
|
>
|
|
between {formatDate(p.start, "long")} and {formatDate(p.end, "long")}.
|
|
</p>
|
|
{:else}
|
|
<p class="px-3 py-2 text-[13px]">
|
|
Account balance of <strong>{account?.name}</strong> at the end of
|
|
{formatDate(addDays(p.start, -1), "long")}
|
|
was:
|
|
<span class="num font-semibold">{money(code, before ?? 0)}</span>
|
|
</p>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full border-collapse text-[13px]">
|
|
<thead>
|
|
<tr class="text-left text-[12px] text-cladd-fg-soft">
|
|
{#if show("icon")}
|
|
<th class="h-7 w-8 px-2"><span class="sr-only">Type</span></th>
|
|
{/if}
|
|
{#if show("description")}
|
|
<th class="h-7 px-3 font-medium">Description</th>
|
|
{/if}
|
|
{#if show("balance_before")}
|
|
<th class="h-7 px-3 text-right font-medium">Balance before</th>
|
|
{/if}
|
|
{#if show("amount")}
|
|
<th class="h-7 px-3 text-right font-medium">Amount</th>
|
|
{/if}
|
|
{#if show("balance_after")}
|
|
<th class="h-7 px-3 text-right font-medium">Balance after</th>
|
|
{/if}
|
|
{#if show("date")}
|
|
<th class="h-7 px-3 font-medium">Date</th>
|
|
{/if}
|
|
{#if show("from")}
|
|
<th class="h-7 px-3 font-medium">From</th>
|
|
{/if}
|
|
{#if show("to")}
|
|
<th class="h-7 px-3 font-medium">To</th>
|
|
{/if}
|
|
{#if show("budget")}
|
|
<th class="h-7 px-3 font-medium">Budget</th>
|
|
{/if}
|
|
{#if show("category")}
|
|
<th class="h-7 px-3 font-medium">Category</th>
|
|
{/if}
|
|
{#if show("bill")}
|
|
<th class="h-7 px-3 font-medium">Subscription</th>
|
|
{/if}
|
|
{#if show("created_at")}
|
|
<th class="h-7 px-3 font-medium">Created at</th>
|
|
{/if}
|
|
{#if show("updated_at")}
|
|
<th class="h-7 px-3 font-medium">Updated at</th>
|
|
{/if}
|
|
{#if show("notes")}
|
|
<th class="h-7 px-3 font-medium">Notes</th>
|
|
{/if}
|
|
{#each dateColumns as key (key)}
|
|
<th class="h-7 px-3 font-medium">
|
|
{key.replace("_date", "").replace("process", "processing")}
|
|
date
|
|
</th>
|
|
{/each}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each rows as row (row.split.transaction_journal_id)}
|
|
{@const s = row.split}
|
|
{@const Icon =
|
|
ICONS[s.type as keyof typeof ICONS] ?? ArrowLeftRight}
|
|
<tr
|
|
class="h-7 border-cladd-outline border-t align-top hover:bg-row-hover"
|
|
>
|
|
{#if show("icon")}
|
|
<td class="px-2 py-1.5 text-cladd-fg-soft">
|
|
<Icon aria-label={s.type} class="size-3.5" />
|
|
</td>
|
|
{/if}
|
|
{#if show("description")}
|
|
<td class="max-w-[22rem] px-3 py-1">
|
|
<a
|
|
class="hover:underline"
|
|
href="/transactions/show/{row.group}"
|
|
>
|
|
{#if row.title}
|
|
{row.title}
|
|
({s.description})
|
|
{:else}
|
|
{s.description}
|
|
{/if}
|
|
</a>
|
|
</td>
|
|
{/if}
|
|
{#if show("balance_before")}
|
|
<td
|
|
class="num whitespace-nowrap px-3 py-1 text-right text-cladd-fg-soft"
|
|
>
|
|
{money(code, row.before)}
|
|
</td>
|
|
{/if}
|
|
{#if show("amount")}
|
|
<td
|
|
class="num whitespace-nowrap px-3 py-1 text-right {row.amount <
|
|
0
|
|
? "text-expense"
|
|
: "text-income"}"
|
|
>
|
|
{money(code, row.amount)}
|
|
</td>
|
|
{/if}
|
|
{#if show("balance_after")}
|
|
<td class="num whitespace-nowrap px-3 py-1 text-right">
|
|
{money(code, row.after)}
|
|
</td>
|
|
{/if}
|
|
{#if show("date")}
|
|
<td class="num whitespace-nowrap px-3 py-1">
|
|
{formatDate(s.date, "medium")}
|
|
</td>
|
|
{/if}
|
|
{#if show("from")}
|
|
<td class="max-w-[12rem] truncate px-3 py-1">
|
|
<a class="hover:underline" href="/accounts/show/{s.source_id}"
|
|
>{s.source_name}</a
|
|
>
|
|
</td>
|
|
{/if}
|
|
{#if show("to")}
|
|
<td class="max-w-[12rem] truncate px-3 py-1">
|
|
<a
|
|
class="hover:underline"
|
|
href="/accounts/show/{s.destination_id}"
|
|
>{s.destination_name}</a
|
|
>
|
|
</td>
|
|
{/if}
|
|
{#if show("budget")}
|
|
<td class="px-3 py-1">{s.budget_name ?? ""}</td>
|
|
{/if}
|
|
{#if show("category")}
|
|
<td class="px-3 py-1">{s.category_name ?? ""}</td>
|
|
{/if}
|
|
{#if show("bill")}
|
|
<td class="px-3 py-1">{s.bill_name ?? ""}</td>
|
|
{/if}
|
|
{#if show("created_at")}
|
|
<td class="num whitespace-nowrap px-3 py-1 text-cladd-fg-soft">
|
|
{formatDateTime(row.created)}
|
|
</td>
|
|
{/if}
|
|
{#if show("updated_at")}
|
|
<td class="num whitespace-nowrap px-3 py-1 text-cladd-fg-soft">
|
|
{formatDateTime(row.updated)}
|
|
</td>
|
|
{/if}
|
|
{#if show("notes")}
|
|
<td
|
|
class="max-w-[24rem] whitespace-pre-line px-3 py-1 text-[12px] text-cladd-fg-soft"
|
|
>
|
|
{s.notes ?? ""}
|
|
</td>
|
|
{/if}
|
|
{#each dateColumns as key (key)}
|
|
<td class="num whitespace-nowrap px-3 py-1">
|
|
{formatDate(s[key] ?? null, "medium")}
|
|
</td>
|
|
{/each}
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="border-cladd-outline border-t px-3 py-2 text-[13px]">
|
|
Account balance of <strong>{account?.name}</strong> at the end of
|
|
{formatDate(p.end, "long")}
|
|
was:
|
|
<span class="num font-semibold">{money(code, after ?? 0)}</span>
|
|
</p>
|
|
{/if}
|
|
</Box>
|