some more updates

This commit is contained in:
2025-07-25 17:34:32 -04:00
parent 63a6694507
commit f4103953f6
5 changed files with 156 additions and 86 deletions

View File

@ -138,24 +138,15 @@ export async function deleteBudgetTransaction(id) {
} }
export async function addBudgetTransaction(budgetId, transactionId, amount, notes, ruleId = null) { export async function addBudgetTransaction(budgetId, transactionId, amount, notes, ruleId = null) {
const existingTransactions = await db`
select amount from budget_transaction const exsisting = await db`
where transaction_id = ${transactionId} select id from budget_transaction
where budget_id = ${budgetId} and transaction_id = ${transactionId}
`; `;
const realTransactionAmount = await db` if (exsisting.length > 0) {
select amount from transaction // If the transaction already exists in the budget, update it
where id = ${transactionId} return updateBudgetTransaction(exsisting[0].id, amount, notes);
`; }
if (existingTransactions.length > 0) {
if (
existingTransactions.reduce((acc, curr) => acc + curr.amount, 0) + amount >
realTransactionAmount
) {
return -1;
}
}
// Add a transaction to a budget // Add a transaction to a budget
const result = await db` const result = await db`
insert into budget_transaction (budget_id, transaction_id, amount, notes, rule_id) insert into budget_transaction (budget_id, transaction_id, amount, notes, rule_id)

View File

@ -1,20 +1,30 @@
<script> <script>
import { invalidateAll } from '$app/navigation'; import { invalidateAll } from '$app/navigation';
let { transaction, close, budgets } = $props();
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { EditSymbol } from '$lib/editSymbol.svelte';
import { TrashBin } from '$lib/trashbin.svelte';
const addToast = getContext('addToast'); const addToast = getContext('addToast');
let { transaction, close, budgets } = $props();
let id = $derived(transaction.id); let id = $derived(transaction.id);
let description = $derived(transaction.description || 'No description'); let description = $derived(transaction.description || 'No description');
let amount = $derived(transaction.amount || 0); let amount = $derived(transaction.amount || 0);
let date = $derived(new Date(transaction.date || Date.now())); let date = $derived(new Date(transaction.date || Date.now()));
let out_of_budget = $derived(transaction.out_of_budget || false); let out_of_budget = $derived(transaction.out_of_budget || false);
let budget_id = $derived(transaction.budget_id || null); let budget_id = $derived(transaction.budget_id || null);
let loading = $state(false); let loading = $state(false);
let deleting = $state(false);
let deletingText = $state('');
let deletebt = $state(null);
let notes = $state(transaction.notes || ''); let notes = $state(transaction.notes || '');
function editBudget(budgetTransaction) {
budget_id = budgetTransaction.budget_id;
amount = budgetTransaction.amount;
notes = budgetTransaction.notes || '';
}
async function saveNotes() { async function saveNotes() {
loading = true; loading = true;
let res = fetch(`/transcation/${id}`, { let res = fetch(`/transcation/${id}`, {
@ -38,6 +48,61 @@
loading = false; loading = false;
close(); close();
} }
async function saveBudget() {
if (budget_id) {
loading = true;
fetch(`/api/budget/${budget_id}/transaction`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactionId: id,
amount: amount,
notes: notes
})
}).then((res) => {
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');
}
loading = false;
close();
}
async function deleteBudgetTransaction(bt) {
deleting = true;
deletingText = `${bt.budget_name}: ${bt.amount}`;
deletebt = bt;
}
async function sendDeletebt() {
loading = true;
if (deletebt) {
let res = await fetch(`/api/budget/${deletebt.id}/transaction`, {
method: 'DELETE'
});
if (res.ok) {
addToast('Budget transaction deleted successfully', 'success');
invalidateAll();
} else {
addToast('Failed to delete budget transaction', 'error');
}
}
deleting = false;
loading = false;
close();
}
</script> </script>
<dialog id="transactionEditModal" class="modal modal-open modal-top}"> <dialog id="transactionEditModal" class="modal modal-open modal-top}">
@ -65,60 +130,56 @@
Out of Budgets Out of Budgets
</label> </label>
<button class="btn btn-neutral" onclick={() => saveNotes()}>Save</button> <button class="btn btn-neutral" onclick={() => saveNotes()}>Save Transaction</button>
<legend class="fieldset-legend">Add to budget</legend> <legend class="fieldset-legend">Current Budgets</legend>
<select bind:value={budget_id} class="select">
<option disabled selected>Pick a budget</option> <div class="flex flex-col">
{#each budgets as budget} {#each transaction.budgetTransactions as budgetTransaction}
<option value={budget.id}>{budget.name} - {budget.sum}</option> <div class="flex justify-between">
<span class="text-lg"
>{budgetTransaction.budget_name}: {budgetTransaction.amount}</span
>
<div class="grow justify-end flex">
<button
class="btn btn-square btn-ghost"
onclick={() => editBudget(budgetTransaction)}
>
{@render EditSymbol()}
</button>
</div>
<div>
<button
class="btn btn-square btn-ghost"
onclick={() => deleteBudgetTransaction(budgetTransaction)}
>
{@render TrashBin()}
</button>
</div>
</div>
{/each} {/each}
</select> </div>
<legend class="fieldset-legend">Amount</legend>
<input {#if deleting}
bind:value={amount} <legend class="fieldset-legend">Delete Budget Transaction</legend>
type="number" <span class="text-xl">Deleting budget transaction - {deletingText}</span>
class="input validator" <span class="text-xl">Are you sure?</span>
required <button class="text-xl btn btn-success" onclick={() => (deleting = false)}>Cancel</button>
placeholder="Amount" <button class="text-xl btn btn-error" onclick={() => sendDeletebt()}>Delete</button>
title="Amount" {:else}
/> <legend class="fieldset-legend">Add to budget</legend>
<legend class="fieldset-legend">Notes</legend> <select bind:value={budget_id} class="select">
<textarea bind:value={notes} class="textarea w-100"></textarea> <option disabled selected>Pick a budget</option>
<p class="validator-hint">Must a sensible number</p> {#each budgets as budget}
<button <option value={budget.id}>{budget.name} - {budget.sum}</option>
class="btn btn-primary" {/each}
onclick={() => { </select>
if (budget_id) { <legend class="fieldset-legend">Amount</legend>
loading = true; <input bind:value={amount} type="number" required placeholder="Amount" title="Amount" />
fetch(`/api/budget/${budget_id}/transaction`, { <legend class="fieldset-legend">Notes</legend>
method: 'POST', <textarea bind:value={notes} class="textarea w-100"></textarea>
headers: { <button class="btn btn-primary" onclick={() => saveBudget()}>Save Budget</button>
'Content-Type': 'application/json' {/if}
},
body: JSON.stringify({
transactionId: id,
amount: amount,
notes: notes
})
}).then((res) => {
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');
}
loading = false;
close();
}}>Add to Budget</button
>
</fieldset> </fieldset>
{/if} {/if}
</div> </div>

View File

@ -5,9 +5,10 @@
let { transactions, budgetTransactions, budgets } = $props(); let { transactions, budgetTransactions, budgets } = $props();
let editing = $state(false); let editing = $state(false);
function editNotes(transaction, remaining) { function editNotes(transaction, remaining, budgetTransactions) {
currentTransaction = transaction; currentTransaction = transaction;
currentTransaction.amount = remaining; currentTransaction.amount = remaining;
currentTransaction.budgetTransactions = budgetTransactions;
editing = true; editing = true;
} }
</script> </script>
@ -65,7 +66,7 @@
<div class=""> <div class="">
<button <button
class="btn btn-square btn-ghost" class="btn btn-square btn-ghost"
onclick={() => editNotes(transaction, remaining)} onclick={() => editNotes(transaction, remaining, applicableBudgets)}
> >
{@render EditSymbol()} {@render EditSymbol()}
</button> </button>

View File

@ -8,20 +8,23 @@
let underAllocatedTrans = $derived(data.underAllocatedTrans); let underAllocatedTrans = $derived(data.underAllocatedTrans);
let budgets = $derived(data.budgets); let budgets = $derived(data.budgets);
let budgetTransactions = $derived(data.budgetTransactions); let budgetTransactions = $derived(data.budgetTransactions);
let last30days = $derived(data.last30DaysTransactionsSums); let last30days = $derived(data.last30DaysTransactionsSums.reverse());
$inspect(last30days);
let chartData = $derived( let chartData = $derived(
last30days last30days.reduce(
.reduce((acc, curr) => [...acc, acc[acc.length - 1] + Number(curr.sum)], [Number(total)]) (acc, curr) => [...acc, acc[acc.length - 1] + Number(curr.sum)],
.reverse() [Number(total)]
); )
let chartDates = $derived(
[
'now',
...last30days.map((day) => `${day.date.getMonth() + 1}/${day.date.getDate()}`)
].reverse()
); );
$inspect(chartData);
let chartDates = $derived([
'now',
...last30days.map((day) => `${day.date.getMonth() + 1}/${day.date.getDate()}`)
]);
const option = $derived({ const option = $derived({
xAxis: { xAxis: {
type: 'category', type: 'category',
@ -33,7 +36,23 @@
series: [ series: [
{ {
data: chartData, data: chartData,
type: 'line' type: 'line',
label: {
show: false,
position: 'top',
formatter: (params) => {
return `$${params.value.toFixed(2)}`;
},
fontSize: 20,
padding: 10,
backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderRadius: 5
},
emphasis: {
label: {
show: true // Labels appear on hover
}
}
} }
] ]
}); });

View File

@ -22,7 +22,6 @@ export async function PATCH({ params, request }) {
const { amount, notes, transactionId } = body; const { amount, notes, transactionId } = body;
console.log({ slug, transactionId, amount }); console.log({ slug, transactionId, amount });
// Call the deleteBudget function from db.js (budgetId, transactionId, amount)
return updateBudgetTransaction(transactionId, amount, notes) return updateBudgetTransaction(transactionId, amount, notes)
.then(() => new Response(`Budget transaction updated successfully`, { status: 200 })) .then(() => new Response(`Budget transaction updated successfully`, { status: 200 }))
.catch( .catch(
@ -36,7 +35,6 @@ export async function DELETE({ params, request }) {
const { transactionId } = slug; const { transactionId } = slug;
console.log({ slug }); console.log({ slug });
// Call the deleteBudget function from db.js (budgetId, transactionId)
return deleteBudgetTransaction(slug) return deleteBudgetTransaction(slug)
.then(() => new Response(`Budget transaction deleted successfully`, { status: 200 })) .then(() => new Response(`Budget transaction deleted successfully`, { status: 200 }))
.catch( .catch(