Getting decent
This commit is contained in:
@ -210,7 +210,7 @@ export async function pullData(amount = 100) {
|
|||||||
for (const transaction of transactions) {
|
for (const transaction of transactions) {
|
||||||
const amount = Number(transaction.amount);
|
const amount = Number(transaction.amount);
|
||||||
const date = new Date(transaction.postedDate);
|
const date = new Date(transaction.postedDate);
|
||||||
const id = transaction.transactionId;
|
const id = transaction.hostTranNumber;
|
||||||
const payee = transaction.description || '';
|
const payee = transaction.description || '';
|
||||||
const statementDescription = transaction.statementDescription;
|
const statementDescription = transaction.statementDescription;
|
||||||
const card = cardRegEx.test(statementDescription)
|
const card = cardRegEx.test(statementDescription)
|
||||||
|
|||||||
@ -1,14 +1,25 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
import { setContext } from 'svelte';
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
let budgets = $derived(data.budgets);
|
let budgets = $derived(data.budgets);
|
||||||
let total = $derived(data.total);
|
let total = $derived(data.total);
|
||||||
|
let toast = $state([]);
|
||||||
|
|
||||||
|
function addToast(message, type = 'info') {
|
||||||
|
toast.push({ message, type });
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.pop();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext('addToast', addToast);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="drawer lg:drawer-open">
|
<div class="drawer lg:drawer-open">
|
||||||
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
|
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
|
||||||
<div class="drawer-content flex flex-col m-5">
|
<div class="drawer-content flex flex-col m-5">
|
||||||
<div class="navbar bg-base-100 shadow-sm">
|
<div class="navbar bg-base-100 shadow-sm lg:hidden">
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<label
|
<label
|
||||||
for="my-drawer-2"
|
for="my-drawer-2"
|
||||||
@ -62,6 +73,15 @@
|
|||||||
<li><div class="divider"></div></li>
|
<li><div class="divider"></div></li>
|
||||||
<li><a href="/rules">Rules</a></li>
|
<li><a href="/rules">Rules</a></li>
|
||||||
<li><a href="/settings">Settings</a></li>
|
<li><a href="/settings">Settings</a></li>
|
||||||
|
<li><a href="/united">United</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="toast toast-top toast-center">
|
||||||
|
{#each toast as t}
|
||||||
|
<div class="alert alert-{t.type}">
|
||||||
|
<span>{t.message}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
import { EditSymbol } from '$lib/editSymbol.svelte';
|
import { EditSymbol } from '$lib/editSymbol.svelte';
|
||||||
import { settingsSymbol } from '$lib/settingsSymbol.svelte';
|
import { settingsSymbol } from '$lib/settingsSymbol.svelte';
|
||||||
import { invalidate, invalidateAll } from '$app/navigation';
|
import { invalidate, invalidateAll } from '$app/navigation';
|
||||||
|
import { loadingModal } from '$lib/loadingModal.svelte';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
const addToast = getContext('addToast');
|
||||||
let trans = $derived(data.transactions);
|
let trans = $derived(data.transactions);
|
||||||
let budgets = $derived(data.budgets);
|
let budgets = $derived(data.budgets);
|
||||||
let budgetTransactions = $derived(data.budgetTransactions);
|
let budgetTransactions = $derived(data.budgetTransactions);
|
||||||
@ -12,6 +16,7 @@
|
|||||||
let hide = $derived(account?.hide || false);
|
let hide = $derived(account?.hide || false);
|
||||||
let inTotal = $derived(account?.in_total || false);
|
let inTotal = $derived(account?.in_total || false);
|
||||||
let expanded = $state([]);
|
let expanded = $state([]);
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
function editNotes(transaction) {
|
function editNotes(transaction) {
|
||||||
my_modal_3.showModal();
|
my_modal_3.showModal();
|
||||||
@ -19,6 +24,8 @@
|
|||||||
notes = transaction.notes;
|
notes = transaction.notes;
|
||||||
}
|
}
|
||||||
async function saveNotes() {
|
async function saveNotes() {
|
||||||
|
my_modal_3.close();
|
||||||
|
loading = true;
|
||||||
let res = fetch(`/transcation/${currentTransaction.id}`, {
|
let res = fetch(`/transcation/${currentTransaction.id}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -26,18 +33,22 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ notes: $state.snapshot(notes) })
|
body: JSON.stringify({ notes: $state.snapshot(notes) })
|
||||||
});
|
});
|
||||||
my_modal_3.close();
|
loading = false;
|
||||||
let result = await res;
|
let result = await res;
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
// Update the transaction in the data
|
// Update the transaction in the data
|
||||||
currentTransaction.notes = notes;
|
currentTransaction.notes = notes;
|
||||||
// Optionally, you can also update the UI or show a success message
|
// Optionally, you can also update the UI or show a success message
|
||||||
|
addToast('Notes saved successfully', 'success');
|
||||||
} else {
|
} else {
|
||||||
// Handle error case
|
// Handle error case
|
||||||
console.error('Failed to save notes');
|
console.error('Failed to save notes');
|
||||||
|
addToast('Failed to save notes', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
|
loading = true;
|
||||||
|
settings_modal.close();
|
||||||
let res = await fetch(`/api/account/${account.id}`, {
|
let res = await fetch(`/api/account/${account.id}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -48,8 +59,9 @@
|
|||||||
in_total: $state.snapshot(inTotal)
|
in_total: $state.snapshot(inTotal)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
loading = false;
|
||||||
|
invalidateAll();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
settings_modal.close();
|
|
||||||
// Optionally, you can refresh the account data or show a success message
|
// Optionally, you can refresh the account data or show a success message
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to save settings');
|
console.error('Failed to save settings');
|
||||||
@ -59,9 +71,9 @@
|
|||||||
|
|
||||||
<div class="flex mb-4">
|
<div class="flex mb-4">
|
||||||
<div class="w-128 flex-none justify-bottom">
|
<div class="w-128 flex-none justify-bottom">
|
||||||
<h1 class="text-xl font-bold">{account?.name}</h1>
|
<h1 class="text-lg font-semibold">{account?.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-64 grow">{account?.balance}</div>
|
<div class="w-64 grow text-lg uppercase font-semibold">{account?.balance}</div>
|
||||||
<div class="w-14 flex-none text-right">
|
<div class="w-14 flex-none text-right">
|
||||||
<button class="btn btn-square btn-ghost" onclick={() => settings_modal.showModal()}
|
<button class="btn btn-square btn-ghost" onclick={() => settings_modal.showModal()}
|
||||||
>{@render settingsSymbol()}
|
>{@render settingsSymbol()}
|
||||||
@ -70,63 +82,44 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>Transcations</h1>
|
<h1>Transcations</h1>
|
||||||
<table class="table w-full">
|
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||||
<thead>
|
{#each trans as transaction}
|
||||||
<tr>
|
{@const applicableBudgets = budgetTransactions.filter(
|
||||||
<th>Date</th>
|
(bt) => bt.transaction_id === transaction.id
|
||||||
<th>Payee</th>
|
)}
|
||||||
<th>Description</th>
|
{@const budgetTotal = applicableBudgets.reduce(
|
||||||
<th>Amount</th>
|
(accumulator, currentValue) => accumulator + Number(currentValue.amount),
|
||||||
<th>Notes</th>
|
0
|
||||||
<th>Budgets</th>
|
)}
|
||||||
<th></th>
|
{@const remaining = transaction.amount - budgetTotal}
|
||||||
</tr>
|
<li
|
||||||
</thead>
|
class="list-row {remaining != 0 ? 'bg-warning-content' : ''} {transaction.pending
|
||||||
<tbody>
|
? 'opacity-50'
|
||||||
{#each trans as transaction}
|
: ''}"
|
||||||
{@const applicableBudgets = budgetTransactions.filter(
|
>
|
||||||
(bt) => bt.transaction_id === transaction.id
|
<div>
|
||||||
)}
|
<div>{transaction.description}</div>
|
||||||
{@const budgetTotal = applicableBudgets.reduce(
|
<div class="text-xs uppercase font-semibold opacity-60">
|
||||||
(accumulator, currentValue) => accumulator + Number(currentValue.amount),
|
{transaction.date.toDateString()}
|
||||||
0
|
</div>
|
||||||
)}
|
</div>
|
||||||
<tr class="hover:bg-base-300">
|
|
||||||
<td>{transaction.date.toDateString()}</td>
|
<div></div>
|
||||||
<td>{transaction.payee ?? ''}</td>
|
<div class="w-32">
|
||||||
<td>{transaction.description}</td>
|
<div class="text-xs uppercase font-semibold text-left">
|
||||||
<td
|
In Budget: {budgetTotal.toFixed(2)}
|
||||||
><ul class="list bg-base-100 rounded-box shadow-md">
|
</div>
|
||||||
<li class="list-row">Amount: {transaction.amount}</li>
|
<div class="text-xs uppercase font-semibold text-left">
|
||||||
<li class="list-row">Budget: {budgetTotal.toFixed(2)}</li>
|
Remaining {remaining.toFixed(2)}
|
||||||
<li class="list-row">Remains: {(transaction.amount - budgetTotal).toFixed(2)}</li>
|
</div>
|
||||||
</ul></td
|
</div>
|
||||||
>
|
<div class="text-lg uppercase font-semibold text-right w-32">{transaction.amount}</div>
|
||||||
<td>{transaction.notes}</td>
|
<button class="btn btn-square btn-ghost" onclick={() => editNotes(transaction)}>
|
||||||
<td>
|
{@render EditSymbol()}
|
||||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
</button>
|
||||||
{#each applicableBudgets as budgetTransaction}
|
</li>
|
||||||
<li class="list-row">
|
{/each}
|
||||||
<div class="flex">
|
</ul>
|
||||||
<div class="flex-auto w-24">
|
|
||||||
{budgets.find((b) => b.id === budgetTransaction.budget_id)?.name}
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto w-16">${budgetTransaction.amount}</div>
|
|
||||||
<div class="flex-auto w-48">{budgetTransaction.notes}</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul></td
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
><button class="btn btn-square btn-ghost" onclick={() => editNotes(transaction)}
|
|
||||||
>{@render EditSymbol()}</button
|
|
||||||
></td
|
|
||||||
>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<dialog id="my_modal_3" class="modal">
|
<dialog id="my_modal_3" class="modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
@ -165,6 +158,8 @@
|
|||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (currentTransaction.budget_id) {
|
if (currentTransaction.budget_id) {
|
||||||
|
loading = true;
|
||||||
|
my_modal_3.close();
|
||||||
fetch(`/api/budget/${currentTransaction.budget_id}/transaction`, {
|
fetch(`/api/budget/${currentTransaction.budget_id}/transaction`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -176,10 +171,14 @@
|
|||||||
notes: currentTransaction.notes
|
notes: currentTransaction.notes
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
|
loading = false;
|
||||||
|
invalidateAll();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
// Optionally, you can refresh the UI or show a success message
|
// Optionally, you can refresh the UI or show a success message
|
||||||
|
addToast('Transaction added to budget', 'success');
|
||||||
console.log('Transaction added to budget successfully');
|
console.log('Transaction added to budget successfully');
|
||||||
} else {
|
} else {
|
||||||
|
addToast('Failed to add transaction to budget', 'error');
|
||||||
console.error('Failed to add transaction to budget');
|
console.error('Failed to add transaction to budget');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -218,3 +217,7 @@
|
|||||||
<button>close</button>
|
<button>close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
{@render loadingModal()}
|
||||||
|
{/if}
|
||||||
|
|||||||
18
src/routes/api/budget/+server.js
Normal file
18
src/routes/api/budget/+server.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import { addBudget, deleteBudget } from '$lib/db';
|
||||||
|
|
||||||
|
// In-memory store for demonstration (replace with a database in production)
|
||||||
|
let budgets = [];
|
||||||
|
|
||||||
|
/** @type {import('./$types').RequestHandler} */
|
||||||
|
export async function POST({ request }) {
|
||||||
|
const data = await request.json();
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!data.name || typeof data.amount !== 'number') {
|
||||||
|
return json({ error: 'Invalid input' }, { status: 400 });
|
||||||
|
}
|
||||||
|
let res = await addBudget(data.name, data.amount, data.notes);
|
||||||
|
|
||||||
|
return json(res, { status: 201 });
|
||||||
|
}
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
|
||||||
import { addBudget, deleteBudget } from '$lib/db';
|
|
||||||
|
|
||||||
// In-memory store for demonstration (replace with a database in production)
|
|
||||||
let budgets = [];
|
|
||||||
|
|
||||||
/** @type {import('./$types').RequestHandler} */
|
|
||||||
export async function POST({ request }) {
|
|
||||||
const data = await request.json();
|
|
||||||
|
|
||||||
// Basic validation
|
|
||||||
if (!data.name || typeof data.amount !== 'number') {
|
|
||||||
return json({ error: 'Invalid input' }, { status: 400 });
|
|
||||||
}
|
|
||||||
let res = await addBudget(data.name, data.amount, data.notes);
|
|
||||||
|
|
||||||
return json(res, { status: 201 });
|
|
||||||
}
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
async function saveBudget() {
|
async function saveBudget() {
|
||||||
loading = true;
|
loading = true;
|
||||||
let res = await fetch('/budget', {
|
let res = await fetch('/api/budget', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -36,14 +36,6 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update() {
|
|
||||||
loading = true;
|
|
||||||
let res = await fetch('/api/simplefin/update', {
|
|
||||||
method: 'POST'
|
|
||||||
});
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteBudget(name) {
|
async function deleteBudget(name) {
|
||||||
loading = true;
|
loading = true;
|
||||||
if (name === toDeleteBudget.name) {
|
if (name === toDeleteBudget.name) {
|
||||||
@ -69,8 +61,6 @@
|
|||||||
{@render loadingModal()}
|
{@render loadingModal()}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button class="btn btn-primary" onclick={update}>Update</button>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<h1 class="text-2xl font-bold">Settings</h1>
|
<h1 class="text-2xl font-bold">Settings</h1>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user