Structure coming along

This commit is contained in:
Casey Timm
2025-07-05 10:07:47 -04:00
parent 7900ecaaf8
commit 824a23765e
15 changed files with 1115 additions and 67 deletions

2
src/app.css Normal file
View File

@ -0,0 +1,2 @@
@import "tailwindcss";
@plugin "daisyui";

170
src/lib/db.js Normal file
View File

@ -0,0 +1,170 @@
// db.js
import postgres from 'postgres'
const db = postgres({ host:'192.168.1.126', username:'budget', password:'budget', database:'budget'}) // will use psql environment variables
export async function createBudgetTable() {
return await db`
create table if not exists budget (
id serial primary key,
name text not null,
amount numeric(10,2) not null,
notes text
)
`
}
export async function createBudgetTransactionTable() {
return await db`
create table if not exists budget_transaction (
id serial primary key,
budget_id integer not null references budget(id),
transaction_id text not null references transaction(id),
amount numeric(10,2) not null
)`
}
export async function addBudget(name, amount, notes) {
const result = await db`
insert into budget (name, amount, notes)
values (${name}, ${amount}, ${notes})
returning id
`
// result = Result [{ id: 1 }]
return result[0].id
}
export async function deleteBudget(id) {
const result = await db`
delete from budget
where id = ${id}
`
// result = Result [{ id: 1 }]
return result
}
export async function updateBudget(id, name, amount, notes) {
const result = await db`
update budget
set name = ${name},
amount = ${amount},
notes = ${notes}
where id = ${id}
`
// result = Result [{ id: 1 }]
return result
}
export async function getBudgetTransactions(id) {
// Fetch all transactions associated with a specific budget
try {
const transactions = await db`
select
transaction.id as id,
transaction.posted as posted,
transaction.amount as amount,
transaction.description as description,
transaction.pending as pending,
transaction.notes as notes,
budget_transaction.amount as budget_amount,
budget_transaction.id as budget_transaction_id
from budget_transaction
join transaction on budget_transaction.transaction_id = transaction.id
where budget_transaction.budget_id = ${id}
order by transaction.posted desc
`
// transactions = Result [{ id: 1, posted: 1633036800, amount: 50.00, description: "Grocery Store", pending: false, notes: "Weekly groceries" }, ...]
return { transactions }
}
catch {
await createBudgetTransactionTable();
return []
}
}
export async function deleteBudgetTransaction(budgetId, transactionId) {
// Delete a transaction from a budget
const result = await db`
delete from budget_transaction
where budget_id = ${budgetId} and transaction_id = ${transactionId}
`
// result = Result [{ id: 1 }]
return result
}
export async function addBudgetTransaction(budgetId, transactionId, amount) {
// Add a transaction to a budget
const result = await db`
insert into budget_transaction (budget_id, transaction_id, amount)
values (${budgetId}, ${transactionId}, ${amount})
returning id
`
// result = Result [{ id: 1 }]
return result[0].id
}
export async function getBudgets() {
const budgets = await db`
select
budget.id as id,
budget.name as name,
budget.amount as amount,
budget.notes as notes
from budget
`
if (!budgets) {
await createBudgetTable();
return await getBudgets()
}
// budgets = Result [{ name: "Walter", age: 80 }, { name: 'Murray', age: 68 }, ...]
return budgets
}
export async function getAccounts(age) {
const accounts = await db`
select
account.id as id,
account.name as name,
org.name as org_name,
balance,
available_balance,
balance_date
from account
left join org on org.id = account.org_id
`
// users = Result [{ name: "Walter", age: 80 }, { name: 'Murray', age: 68 }, ...]
return accounts
}
export async function getTransactions(accountId) {
let transactions = await db`
select
transaction.id as id,
transaction.posted as posted,
transaction.amount as amount,
transaction.description as description,
transaction.pending as pending,
transaction.notes as notes
from transaction
where account_id = ${accountId}
order by posted desc
`
transactions = transactions.map((t) => ({
...t, date: new Date(t.posted * 1000)
}));
return transactions
}
export async function setTransactionNote(transactionId, note) {
const result = await db`
update transaction
set notes = ${note}
where id = ${transactionId}
`
return result
}
export default db

5
src/lib/simplefin.js Normal file
View File

@ -0,0 +1,5 @@
export async function updateAccounts()
{
fetch
}

View File

@ -0,0 +1,9 @@
import { getAccounts, getBudgets } from "$lib/db";
import { get } from "svelte/store";
export async function load({ params }) {
let accounts = await getAccounts();
let budgets = await getBudgets();
return { accounts, budgets };
}

105
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,105 @@
<script>
import '../app.css';
let { children, data } = $props();
let budgets = $state(data.budgets);
let newBudget = $state({
name: '',
amount: 0,
notes: ''
});
async function saveBudget() {
let res = await fetch('/budget', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newBudget)
});
if (res.ok) {
AddBudgetModal.close();
budgets.push({
id: res.text(), // Temporary ID, replace with actual ID from the server
...newBudget
});
newBudget = { name: '', amount: 0, notes: '' }; // Reset the form
// Optionally, you can refresh the budgets list or show a success message
} else {
console.error('Failed to save budget');
}
}
</script>
<div class="drawer lg:drawer-open">
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col items-center justify-center">
<!-- Page content here -->
{@render children()}
<label for="my-drawer-2" class="btn btn-primary drawer-button lg:hidden"> Open drawer </label>
</div>
<div class="drawer-side">
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu bg-base-200 text-base-content min-h-full w-80 p-4">
{#each data.accounts as account}
<li>
<a href={`/account/${account.id}`}>
{account.name} ({account.balance})
</a>
</li>
{/each}
<li><div class="divider"></div></li>
{#each budgets as budget}
<li>
<a href={`/budget/${budget.id}`}>
{budget.name} ({budget.amount})
</a>
</li>
{/each}
<li>
<button class="btn btn-circle" aria-label="Add" onclick={()=>AddBudgetModal.showModal()}>
<svg
fill="#F0F0F0F0"
height="800px"
width="800px"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 500 500"
enable-background="new 0 0 500 500"
xml:space="preserve"
>
<path
d="M306,192h-48v-48c0-4.4-3.6-8-8-8s-8,3.6-8,8v48h-48c-4.4,0-8,3.6-8,8s3.6,8,8,8h48v48c0,4.4,3.6,8,8,8s8-3.6,8-8v-48h48
c4.4,0,8-3.6,8-8S310.4,192,306,192z"
/>
</svg>
</button>
</li>
</ul>
</div>
</div>
<dialog id="AddBudgetModal" 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>
<div>
<input
bind:value={newBudget.name}
type="text"
placeholder="Budget Name"
class="input input-bordered w-full max-w-xs"/>
<input
bind:value={newBudget.amount}
type="number"
placeholder="Budget Amount"
class="input input-bordered w-full max-w-xs mt-2"/>
<textarea bind:value={newBudget.notes} class="textarea w-100"></textarea>
</div>
<button onclick={() => saveBudget()} class="btn btn-primary mt-4">Save</button>
</div>
</dialog>

View File

@ -1,2 +0,0 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>

View File

@ -0,0 +1,13 @@
import { error } from '@sveltejs/kit';
import { getTransactions } from '$lib/db';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const transactions = await getTransactions(params.slug);
if (transactions) {
return {transactions};
}
error(404, 'Not found');
}

View File

@ -0,0 +1,70 @@
<script>
let { data } = $props();
let trans = $state(data.transactions);
let notes = $state("");
let currentTransaction = $state(null);
function editNotes(transaction) {
my_modal_3.showModal()
currentTransaction = transaction;
notes = transaction.notes;
}
async function saveNotes() {
let res = fetch(`/transcation/${currentTransaction.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ notes:$state.snapshot(notes) })
})
my_modal_3.close();
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
} else {
// Handle error case
console.error("Failed to save notes");
}
}
</script>
<h1>Transcations</h1>
<table class="table w-full">
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{#each trans as transaction}
<tr class="hover:bg-base-300" onclick={() => editNotes(transaction)}>
<td>{transaction.date.toDateString()}</td>
<td>{transaction.description}</td>
<td>{transaction.amount}</td>
<td>{transaction.notes}</td>
</tr>
{/each}
</tbody>
</table>
<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>
<h3>{currentTransaction?.description}</h3>
<h4>${currentTransaction?.amount}</h4>
<p> {currentTransaction?.date?.toDateString()}</p>
<div>
<textarea bind:value={notes} class="textarea w-100"/>
</div>
<button onclick={() => saveNotes()} class="btn btn-primary mt-4">Save</button>
</div>
</dialog>

View File

@ -0,0 +1,18 @@
import { json } from '@sveltejs/kit';
import { addBudget } 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 });
}

View File

@ -0,0 +1,12 @@
import { error } from '@sveltejs/kit';
import { getBudgetTransactions } from '$lib/db.js';
export async function load({ params }) {
const { slug } = params;
const transactions = await getBudgetTransactions(slug);
return { transactions, slug };
}

View File

@ -0,0 +1,35 @@
<script>
let { data } = $props();
let budget = $state(data.budgets.find((b) => b.id == data.slug) || {});
let transactions = $state(data.transactions || []);
</script>
<div class="align-text-top">
<h1 class="text-2xl font-bold">{budget.name}</h1>
<p class="text-lg">Amount: ${budget.amount}</p>
<p class="text-sm">Notes: {budget.notes}</p>
</div>
<table class="min-w-full border border-gray-300">
<thead>
<tr class="">
<th class="px-4 py-2 border-b">Date</th>
<th class="px-4 py-2 border-b">Description</th>
<th class="px-4 py-2 border-b">Amount</th>
</tr>
</thead>
<tbody>
{#each transactions as txn}
<tr>
<td class="px-4 py-2 border-b">{txn.date}</td>
<td class="px-4 py-2 border-b">{txn.description}</td>
<td class="px-4 py-2 border-b">${txn.amount}</td>
</tr>
{/each}
{#if transactions.length === 0}
<tr>
<td class="px-4 py-2 border-b text-center" colspan="3">No transactions found.</td>
</tr>
{/if}
</tbody>
</table>

View File

@ -0,0 +1,14 @@
import {setTransactionNote} from '$lib/db';
export async function POST(request) {
let body = await request.request.json();
let res = await setTransactionNote(request.params.slug, body.notes);
console.log({slug: request.params.slug, notes: body.notes});
console.log(res);
return new Response(JSON.stringify({success: true}), {
headers: {
'Content-Type': 'application/json'
}
});
}