New and imporved!
This commit is contained in:
@ -86,7 +86,7 @@ export async function updateBudget(id, name, amount, notes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getBudgetTransactions(id) {
|
||||
export async function getBudgetTransactionsByid(id) {
|
||||
// Fetch all transactions associated with a specific budget
|
||||
try {
|
||||
let transactions = await db`
|
||||
@ -258,6 +258,22 @@ export async function getBudgetTransactionsForAccount(accountID) {
|
||||
return transactions;
|
||||
}
|
||||
|
||||
export async function getAllBudgetTransactions(accountID) {
|
||||
const transactions = await db`
|
||||
select budget_transaction.id as id,
|
||||
budget_transaction.budget_id as budget_id,
|
||||
budget_transaction.transaction_id as transaction_id,
|
||||
budget_transaction.amount as amount,
|
||||
budget_transaction.notes as notes,
|
||||
budget.name as budget_name
|
||||
from budget_transaction
|
||||
join transaction on budget_transaction.transaction_id = transaction.id
|
||||
join budget on budget_transaction.budget_id = budget.id
|
||||
`;
|
||||
return transactions;
|
||||
}
|
||||
|
||||
|
||||
export async function getHiddenAccounts(age) {
|
||||
const accounts = await db`
|
||||
select
|
||||
@ -514,4 +530,63 @@ export async function runRules() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUnallocatedTotal() {
|
||||
const result = await db`
|
||||
select sum(amount) as total
|
||||
from budget_transaction
|
||||
where transaction_id is null
|
||||
`;
|
||||
// result = Result [{ total: 1000.00 }]
|
||||
return result[0].total;
|
||||
}
|
||||
|
||||
export async function getUnallocatedTransactions() {
|
||||
const result = await db`
|
||||
SELECT TRANSACTION.id as id,
|
||||
TRANSACTION.amount as amount,
|
||||
TRANSACTION.description as description,
|
||||
TRANSACTION.date as date,
|
||||
TRANSACTION.account_id
|
||||
FROM TRANSACTION
|
||||
full join budget_transaction
|
||||
ON TRANSACTION.id = budget_transaction.transaction_id
|
||||
WHERE budget_transaction.amount IS NULL
|
||||
AND TRANSACTION.out_of_budget = FALSE
|
||||
`;
|
||||
// result = Result [{ id: 1, amount: 100.00, notes: "Unallocated funds" }, ...]
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getUnderallocatedTransactions() {
|
||||
const result = await db`
|
||||
SELECT *
|
||||
FROM (SELECT TRANSACTION.id,
|
||||
TRANSACTION.amount AS amount,
|
||||
TRANSACTION.description,
|
||||
TRANSACTION.DATE AS date,
|
||||
TRANSACTION.account_id,
|
||||
SUM(budget_transaction.amount) AS budget_total
|
||||
FROM TRANSACTION
|
||||
full join budget_transaction
|
||||
ON TRANSACTION.id = budget_transaction.transaction_id
|
||||
WHERE TRANSACTION.out_of_budget = FALSE
|
||||
GROUP BY TRANSACTION.id) AS t
|
||||
WHERE t.amount != t.budget_total
|
||||
`;
|
||||
// result = Result [{ id: 1, amount: 100.00, notes: "Unallocated funds" }, ...]
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getLast30DaysTransactionsSums() {
|
||||
let result = await db`
|
||||
SELECT cast(date as date) as date, SUM(amount)
|
||||
from transaction
|
||||
where date >= (CURRENT_DATE - interval '30 days')
|
||||
GROUP BY cast(date as date)
|
||||
order by date desc;
|
||||
`;
|
||||
// result = Result [{ id: 1, amount: 100.00, notes: "Recent transaction" }, ...]
|
||||
return result;
|
||||
}
|
||||
|
||||
export default db;
|
||||
|
||||
6
src/lib/echarts.js
Normal file
6
src/lib/echarts.js
Normal file
@ -0,0 +1,6 @@
|
||||
import * as charts from 'echarts';
|
||||
|
||||
export function echarts(node, option) {
|
||||
const chart = charts.init(node);
|
||||
chart.setOption(option);
|
||||
}
|
||||
128
src/lib/editTransaction.svelte
Normal file
128
src/lib/editTransaction.svelte
Normal file
@ -0,0 +1,128 @@
|
||||
<script>
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
let { transaction, close, budgets } = $props();
|
||||
import { getContext } from 'svelte';
|
||||
const addToast = getContext('addToast');
|
||||
|
||||
let id = $derived(transaction.id);
|
||||
let description = $derived(transaction.description || 'No description');
|
||||
let amount = $derived(transaction.amount || 0);
|
||||
let date = $derived(new Date(transaction.date || Date.now()));
|
||||
let out_of_budget = $derived(transaction.out_of_budget || false);
|
||||
let budget_id = $derived(transaction.budget_id || null);
|
||||
|
||||
let loading = $state(false);
|
||||
|
||||
let notes = $state(transaction.notes || '');
|
||||
|
||||
async function saveNotes() {
|
||||
loading = true;
|
||||
let res = fetch(`/transcation/${id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
notes: $state.snapshot(notes),
|
||||
out_of_budget: out_of_budget
|
||||
})
|
||||
});
|
||||
let result = await res;
|
||||
if (result.ok) {
|
||||
addToast('Notes saved successfully', 'success');
|
||||
invalidateAll();
|
||||
} else {
|
||||
// Handle error case
|
||||
addToast('Failed to save notes', 'error');
|
||||
}
|
||||
loading = false;
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog id="transactionEditModal" class="modal modal-open modal-top}">
|
||||
<div class="modal-box">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center">
|
||||
<span class="loading loading-spinner loading-xl"></span>
|
||||
</div>
|
||||
{:else}
|
||||
<form method="dialog">
|
||||
<button
|
||||
onclick={() => close()}
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button
|
||||
>
|
||||
</form>
|
||||
<fieldset class="fieldset">
|
||||
<p class="label">{description}</p>
|
||||
<p class="label">${amount}</p>
|
||||
<p class="label">{date.toDateString()}</p>
|
||||
<legend class="fieldset-legend">Notes</legend>
|
||||
<textarea bind:value={notes} class="textarea w-100"></textarea>
|
||||
<legend class="fieldset-legend">Transaction Properties</legend>
|
||||
<label class="label">
|
||||
<input type="checkbox" bind:checked={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={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={amount}
|
||||
type="number"
|
||||
class="input validator"
|
||||
required
|
||||
placeholder="Amount"
|
||||
title="Amount"
|
||||
/>
|
||||
<legend class="fieldset-legend">Notes</legend>
|
||||
<textarea bind:value={notes} class="textarea w-100"></textarea>
|
||||
<p class="validator-hint">Must a sensible number</p>
|
||||
<button
|
||||
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>
|
||||
{/if}
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button onclick={() => close()}>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
80
src/lib/transactionList.svelte
Normal file
80
src/lib/transactionList.svelte
Normal file
@ -0,0 +1,80 @@
|
||||
<script>
|
||||
import { EditSymbol } from '$lib/editSymbol.svelte';
|
||||
import EditTransaction from '$lib/editTransaction.svelte';
|
||||
let currentTransaction = $state({ budget_id: null, amount: 0, notes: '', out_of_budget: false });
|
||||
let { transactions, budgetTransactions, budgets } = $props();
|
||||
let editing = $state(false);
|
||||
|
||||
function editNotes(transaction, remaining) {
|
||||
currentTransaction = transaction;
|
||||
currentTransaction.amount = remaining;
|
||||
editing = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#each transactions 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}
|
||||
|
||||
{#if editing}
|
||||
<EditTransaction transaction={currentTransaction} close={() => (editing = false)} budgets />
|
||||
{/if}
|
||||
</div>
|
||||
@ -21,22 +21,23 @@ export function isNeedCode() {
|
||||
export function isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
export async function pullData(amount = 100) {
|
||||
running = true;
|
||||
try {
|
||||
state = [];
|
||||
code = null;
|
||||
needCode = false;
|
||||
state.push('Starting Browser');
|
||||
const browser = await chromium.launchPersistentContext('context', {
|
||||
const browser = await chromium.launchPersistentContext('context', {
|
||||
channel: 'chrome',
|
||||
headless: true,
|
||||
headless: false,
|
||||
viewport: null
|
||||
// do NOT add custom browser headers or userAgent
|
||||
});
|
||||
|
||||
export async function pullData(amount = 100) {
|
||||
running = true;
|
||||
state = [];
|
||||
code = null;
|
||||
needCode = false;
|
||||
state.push('Starting Browser');
|
||||
|
||||
|
||||
const page = await browser.newPage();
|
||||
try {
|
||||
|
||||
state.push('Navigating to United FCU');
|
||||
await page.goto('https://online.unitedfcu.com/unitedfederalcredituniononline/uux.aspx#/login');
|
||||
@ -260,11 +261,10 @@ export async function pullData(amount = 100) {
|
||||
}
|
||||
|
||||
state.push('Done');
|
||||
await browser.close();
|
||||
} catch (error) {
|
||||
console.error('Error in pullData:', error);
|
||||
state.push(`Error: ${error.message}`);
|
||||
}
|
||||
|
||||
page.close();
|
||||
running = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user