Files
budget/src/routes/account/[slug]/+page.svelte
2025-07-21 17:21:02 -04:00

257 lines
7.9 KiB
Svelte

<script>
import { EditSymbol } from '$lib/editSymbol.svelte';
import { settingsSymbol } from '$lib/settingsSymbol.svelte';
import { invalidate, invalidateAll } from '$app/navigation';
import { loadingModal } from '$lib/loadingModal.svelte';
import { getContext } from 'svelte';
let { data } = $props();
const addToast = getContext('addToast');
let trans = $derived(data.transactions);
let budgets = $derived(data.budgets);
let budgetTransactions = $derived(data.budgetTransactions);
let notes = $state('');
let currentTransaction = $state({ budget_id: null, amount: 0, notes: '', out_of_budget: false });
let account = $derived(data.account);
let hide = $derived(account?.hide || false);
let inTotal = $derived(account?.in_total || false);
let expanded = $state([]);
let loading = $state(false);
function editNotes(transaction, remaining) {
my_modal_3.showModal();
currentTransaction = transaction;
notes = transaction.notes;
currentTransaction.amount = remaining;
}
async function saveNotes() {
my_modal_3.close();
loading = true;
let res = fetch(`/transcation/${currentTransaction.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
notes: $state.snapshot(notes),
out_of_budget: currentTransaction.out_of_budget
})
});
loading = false;
let result = await res;
if (result.ok) {
// Update the transaction in the data
currentTransaction.notes = notes;
// Optionally, you can also update the UI or show a success message
addToast('Notes saved successfully', 'success');
invalidateAll();
} else {
// Handle error case
console.error('Failed to save notes');
addToast('Failed to save notes', 'error');
}
}
async function saveSettings() {
loading = true;
settings_modal.close();
let res = await fetch(`/api/account/${account.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
hide: $state.snapshot(hide),
in_total: $state.snapshot(inTotal)
})
});
loading = false;
invalidateAll();
if (res.ok) {
// Optionally, you can refresh the account data or show a success message
} else {
console.error('Failed to save settings');
}
}
</script>
<div class="flex mb-4">
<div class="w-128 flex-none justify-bottom">
<h1 class="text-lg font-semibold">{account?.name}</h1>
</div>
<div class="w-64 grow text-lg uppercase font-semibold">{account?.balance}</div>
<div class="w-14 flex-none text-right">
<button class="btn btn-square btn-ghost" onclick={() => settings_modal.showModal()}
>{@render settingsSymbol()}
</button>
</div>
</div>
<h1>Transcations</h1>
<div>
{#each trans as transaction}
{@const applicableBudgets = budgetTransactions.filter(
(bt) => bt.transaction_id === transaction.id
)}
{@const budgetTotal = applicableBudgets.reduce(
(accumulator, currentValue) => accumulator + Number(currentValue.amount),
0
)}
{@const remaining = transaction.amount - budgetTotal}
<div
class=" p-2 {remaining != 0 && !transaction.out_of_budget
? 'bg-warning-content'
: ''} {transaction.pending ? 'opacity-50' : ''}"
>
<div class="h-full flex flex-row justify-between items-center">
<div class="h-full flex flex-col md:flex-row justify-between md:items-center md:grow">
<div>
<div>{transaction.description}</div>
<div class="text-xs uppercase font-semibold opacity-60">
{transaction.date.toDateString()}
</div>
{#if !transaction.out_of_budget}
<div class="text-xs uppercase font-semibold text-left opacity-60">
In Budget: {budgetTotal.toFixed(2)}
</div>
<div class="text-xs uppercase font-semibold text-left opacity-60">
Remaining {remaining.toFixed(2)}
</div>
{:else}
<div class="text-xs uppercase font-semibold text-left opacity-60">Out of budget</div>
{/if}
</div>
{#if applicableBudgets.length > 0}
<div class="flex grow flex-col">
{#each applicableBudgets as budgetTransaction}
<div class="md:text-right">
{`${budgetTransaction.budget_name}: ${budgetTransaction.amount}`}
</div>
{/each}
</div>
{/if}
<div class="md:text-right text-2xl md:p-4 md:w-35">
<div class="">
{transaction.amount}
</div>
</div>
</div>
<div class="">
<button
class="btn btn-square btn-ghost"
onclick={() => editNotes(transaction, remaining)}
>
{@render EditSymbol()}
</button>
</div>
</div>
</div>
{/each}
</div>
<dialog id="my_modal_3" class="modal">
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<fieldset class="fieldset">
<p class="label">{currentTransaction?.description}</p>
<p class="label">${currentTransaction?.amount}</p>
<p class="label">{currentTransaction?.date?.toDateString()}</p>
<legend class="fieldset-legend">Notes</legend>
<textarea bind:value={notes} class="textarea w-100"></textarea>
<legend class="fieldset-legend">Login options</legend>
<label class="label">
<input type="checkbox" bind:checked={currentTransaction.out_of_budget} class="toggle" />
Out of Budgets
</label>
<button class="btn btn-neutral" onclick={() => saveNotes()}>Save</button>
<legend class="fieldset-legend">Add to budget</legend>
<select bind:value={currentTransaction.budget_id} class="select">
<option disabled selected>Pick a budget</option>
{#each budgets as budget}
<option value={budget.id}>{budget.name} - {budget.sum}</option>
{/each}
</select>
<legend class="fieldset-legend">Amount</legend>
<input
bind:value={currentTransaction.amount}
type="number"
class="input validator"
required
placeholder="Amount"
title="Amount"
/>
<legend class="fieldset-legend">Notes</legend>
<textarea bind:value={currentTransaction.notes} class="textarea w-100"></textarea>
<p class="validator-hint">Must a sensible number</p>
<button
class="btn btn-primary"
onclick={() => {
if (currentTransaction.budget_id) {
loading = true;
my_modal_3.close();
fetch(`/api/budget/${currentTransaction.budget_id}/transaction`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactionId: currentTransaction.id,
amount: currentTransaction.amount,
notes: currentTransaction.notes
})
}).then((res) => {
loading = false;
invalidateAll();
if (res.ok) {
// Optionally, you can refresh the UI or show a success message
addToast('Transaction added to budget', 'success');
console.log('Transaction added to budget successfully');
} else {
addToast('Failed to add transaction to budget', 'error');
console.error('Failed to add transaction to budget');
}
});
} else {
console.error('No budget selected');
}
}}>Add to Budget</button
>
</fieldset>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<dialog id="settings_modal" class="modal">
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<h3>{account?.name}</h3>
<fieldset class="fieldset bg-base-100 border-base-300 rounded-box w-64 border p-4">
<legend class="fieldset-legend">Options</legend>
<label class="label">
<input type="checkbox" bind:checked={hide} class="toggle" />
Hide
</label>
<label class="label">
<input type="checkbox" bind:checked={inTotal} class="toggle" />
Use in total
</label>
</fieldset>
<button onclick={() => saveSettings()} class="btn btn-primary mt-4">Save</button>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{#if loading}
{@render loadingModal()}
{/if}