fix(budgets,tags,piggy-banks,dashboard): live buttons that answer in place, lucide icons, mobile wrapping
This commit is contained in:
1 parent
aba2509399
commit
f3a603648e
10 files changed
+102
-71
No files matched your search
@@ -56,6 +56,7 @@
|
||||
let busy = $state(false);
|
||||
let status = $state("");
|
||||
let failure = $state("");
|
||||
let pressed = $state(false);
|
||||
|
||||
const account = createQuery(() => ({
|
||||
queryFn: () => api<One<Account>>(`/v1/accounts/${id}`),
|
||||
@@ -179,6 +180,17 @@
|
||||
const canCorrect = $derived(Boolean(reconciliationId));
|
||||
const correcting = $derived(correct && canCorrect && difference !== 0);
|
||||
const ready = $derived(before.isSuccess && after.isSuccess && list.isSuccess);
|
||||
const hint = $derived.by(() => {
|
||||
if (!pressed) {
|
||||
return "";
|
||||
}
|
||||
if (!ready) {
|
||||
return "Balances are still loading";
|
||||
}
|
||||
return selected.length === 0 && !correcting
|
||||
? "Tick the transactions to clear first"
|
||||
: "";
|
||||
});
|
||||
|
||||
function setDates(nextStart: string, nextEnd: string) {
|
||||
if (!(nextStart && nextEnd) || nextStart > nextEnd) {
|
||||
@@ -241,7 +253,8 @@
|
||||
}
|
||||
|
||||
async function store() {
|
||||
if (busy || !ready || (selected.length === 0 && !correcting)) {
|
||||
pressed = true;
|
||||
if (busy || hint) {
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
@@ -525,10 +538,7 @@
|
||||
</fieldset>
|
||||
{/if}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
disabled={busy || !ready || (selected.length === 0 && !correcting)}
|
||||
onclick={store}
|
||||
type="button"
|
||||
<Button disabled={busy} onclick={store} type="button"
|
||||
>Store reconciliation</Button
|
||||
>
|
||||
<span class="flex gap-1">
|
||||
@@ -536,6 +546,9 @@
|
||||
<span class="kbd">{key}</span>
|
||||
{/each}
|
||||
</span>
|
||||
{#if hint}
|
||||
<span class="text-[12px] text-expense" role="alert">{hint}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if status}
|
||||
<p class="text-[12px] text-cladd-fg-soft" role="status">{status}</p>
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="truncate font-semibold text-[18px] text-cladd-fg tracking-[-0.02em] sm:text-[22px]"
|
||||
class="wrap-anywhere font-semibold text-[18px] text-cladd-fg tracking-[-0.02em] sm:text-[22px]"
|
||||
>
|
||||
{fmt(list[0], abs)}
|
||||
</div>
|
||||
@@ -142,7 +142,7 @@
|
||||
{:else}
|
||||
<ArrowDown class="size-3.5 shrink-0" />
|
||||
{/if}
|
||||
<span class="truncate"
|
||||
<span
|
||||
><span class="num">{delta.text}</span>
|
||||
<span class="text-cladd-fg-soft">than {prevName}</span></span
|
||||
>
|
||||
@@ -203,18 +203,20 @@
|
||||
segments={[{ color: "var(--ff-series-3)", value: paidShare.paid }]}
|
||||
/>
|
||||
{/if}
|
||||
<div class="truncate text-[12px] text-cladd-fg-soft">
|
||||
Paid:
|
||||
<span class="num"
|
||||
>{paid.length ? paid.map((e) => fmt(e, true)).join(", ") : "0"}</span
|
||||
>
|
||||
<div class="flex flex-wrap gap-x-2 text-[12px] text-cladd-fg-soft">
|
||||
<span>Paid:</span>
|
||||
{#each paid as e (e.code)}
|
||||
<span class="num">{fmt(e, true)}</span>
|
||||
{:else}
|
||||
<span class="num">0</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet leftToSpend()}
|
||||
{@render values(left)}
|
||||
{#if left[0]?.sub}
|
||||
<div class="truncate text-[12px] text-cladd-fg-soft">
|
||||
<div class="text-[12px] text-cladd-fg-soft">
|
||||
{leftState === "none" ? "" : "Per day: "}<span class="num"
|
||||
>{left[0].sub}</span
|
||||
>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
const action = new Action();
|
||||
const values = $state<Record<string, string>>({});
|
||||
const fields: Record<string, HTMLElement> = {};
|
||||
|
||||
const links = $derived(
|
||||
(piggy.attributes.accounts ?? []).map((link) => {
|
||||
@@ -50,9 +51,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function fail(message: string) {
|
||||
function fail(message: string, id = open[0]?.id ?? "") {
|
||||
action.failed = true;
|
||||
action.message = message;
|
||||
fields[id]?.focus();
|
||||
}
|
||||
|
||||
async function save() {
|
||||
@@ -64,11 +66,11 @@
|
||||
}
|
||||
const amount = evalAmount(raw);
|
||||
if (amount === null || amount <= 0) {
|
||||
return fail("Enter an amount above zero");
|
||||
return fail("Enter an amount above zero", link.id);
|
||||
}
|
||||
if (amount > link.max + 1e-9) {
|
||||
const max = formatMoney(piggyMoney(piggy.attributes, link.max));
|
||||
return fail(`${link.name} allows at most ${max}`);
|
||||
return fail(`${link.name} allows at most ${max}`, link.id);
|
||||
}
|
||||
deltas[link.id] = mode === "add" ? amount : -amount;
|
||||
}
|
||||
@@ -109,6 +111,7 @@
|
||||
onkeydown={onEscape}
|
||||
bind:value={values[link.id]}
|
||||
{@attach (node) => {
|
||||
fields[link.id] = node;
|
||||
if (i === 0) {
|
||||
node.focus();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import { formatDate } from "#lib/format.js";
|
||||
import { STATUS_TONE, type Status, statusText } from "./bills.js";
|
||||
|
||||
@@ -9,9 +10,9 @@
|
||||
<span class="inline-flex flex-wrap gap-x-2">
|
||||
{#each state.paid as paid (paid.transaction_journal_id)}
|
||||
<a
|
||||
class="text-income hover:underline"
|
||||
class="inline-flex items-center gap-1 text-income hover:underline"
|
||||
href="/transactions/show/{paid.transaction_group_id}"
|
||||
>✓ {formatDate(paid.date, "short")}</a
|
||||
><Check class="size-3.5 shrink-0" />{formatDate(paid.date, "short")}</a
|
||||
>
|
||||
{/each}
|
||||
</span>
|
||||
|
||||
Reference in new issue
Block a user