some more updates
This commit is contained in:
@ -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
|
|
||||||
where transaction_id = ${transactionId}
|
|
||||||
`;
|
|
||||||
const realTransactionAmount = await db`
|
|
||||||
select amount from transaction
|
|
||||||
where id = ${transactionId}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (existingTransactions.length > 0) {
|
const exsisting = await db`
|
||||||
if (
|
select id from budget_transaction
|
||||||
existingTransactions.reduce((acc, curr) => acc + curr.amount, 0) + amount >
|
where budget_id = ${budgetId} and transaction_id = ${transactionId}
|
||||||
realTransactionAmount
|
`;
|
||||||
) {
|
if (exsisting.length > 0) {
|
||||||
return -1;
|
// If the transaction already exists in the budget, update it
|
||||||
|
return updateBudgetTransaction(exsisting[0].id, amount, notes);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
||||||
|
|||||||
@ -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,8 +130,43 @@
|
|||||||
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">Current Budgets</legend>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
{#each transaction.budgetTransactions as budgetTransaction}
|
||||||
|
<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}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if deleting}
|
||||||
|
<legend class="fieldset-legend">Delete Budget Transaction</legend>
|
||||||
|
<span class="text-xl">Deleting budget transaction - {deletingText}</span>
|
||||||
|
<span class="text-xl">Are you sure?</span>
|
||||||
|
<button class="text-xl btn btn-success" onclick={() => (deleting = false)}>Cancel</button>
|
||||||
|
<button class="text-xl btn btn-error" onclick={() => sendDeletebt()}>Delete</button>
|
||||||
|
{:else}
|
||||||
<legend class="fieldset-legend">Add to budget</legend>
|
<legend class="fieldset-legend">Add to budget</legend>
|
||||||
<select bind:value={budget_id} class="select">
|
<select bind:value={budget_id} class="select">
|
||||||
<option disabled selected>Pick a budget</option>
|
<option disabled selected>Pick a budget</option>
|
||||||
@ -75,50 +175,11 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<legend class="fieldset-legend">Amount</legend>
|
<legend class="fieldset-legend">Amount</legend>
|
||||||
<input
|
<input bind:value={amount} type="number" required placeholder="Amount" title="Amount" />
|
||||||
bind:value={amount}
|
|
||||||
type="number"
|
|
||||||
class="input validator"
|
|
||||||
required
|
|
||||||
placeholder="Amount"
|
|
||||||
title="Amount"
|
|
||||||
/>
|
|
||||||
<legend class="fieldset-legend">Notes</legend>
|
<legend class="fieldset-legend">Notes</legend>
|
||||||
<textarea bind:value={notes} class="textarea w-100"></textarea>
|
<textarea bind:value={notes} class="textarea w-100"></textarea>
|
||||||
<p class="validator-hint">Must a sensible number</p>
|
<button class="btn btn-primary" onclick={() => saveBudget()}>Save Budget</button>
|
||||||
<button
|
{/if}
|
||||||
class="btn btn-primary"
|
|
||||||
onclick={() => {
|
|
||||||
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();
|
|
||||||
}}>Add to Budget</button
|
|
||||||
>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -8,19 +8,22 @@
|
|||||||
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(
|
|
||||||
[
|
$inspect(chartData);
|
||||||
|
let chartDates = $derived([
|
||||||
'now',
|
'now',
|
||||||
...last30days.map((day) => `${day.date.getMonth() + 1}/${day.date.getDate()}`)
|
...last30days.map((day) => `${day.date.getMonth() + 1}/${day.date.getDate()}`)
|
||||||
].reverse()
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
const option = $derived({
|
const option = $derived({
|
||||||
xAxis: {
|
xAxis: {
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user