Structure coming along
This commit is contained in:
718
package-lock.json
generated
718
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,15 @@
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"daisyui": "^5.0.43",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.0.0",
|
||||
"vite": "^6.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"postgres": "^3.4.7",
|
||||
"tailwindcss": "^4.1.11"
|
||||
}
|
||||
}
|
||||
|
||||
2
src/app.css
Normal file
2
src/app.css
Normal file
@ -0,0 +1,2 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
170
src/lib/db.js
Normal file
170
src/lib/db.js
Normal 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
5
src/lib/simplefin.js
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
export async function updateAccounts()
|
||||
{
|
||||
fetch
|
||||
}
|
||||
9
src/routes/+layout.server.js
Normal file
9
src/routes/+layout.server.js
Normal 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
105
src/routes/+layout.svelte
Normal 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>
|
||||
@ -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>
|
||||
|
||||
13
src/routes/account/[slug]/+page.server.js
Normal file
13
src/routes/account/[slug]/+page.server.js
Normal 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');
|
||||
}
|
||||
70
src/routes/account/[slug]/+page.svelte
Normal file
70
src/routes/account/[slug]/+page.svelte
Normal 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>
|
||||
18
src/routes/budget/+server.js
Normal file
18
src/routes/budget/+server.js
Normal 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 });
|
||||
}
|
||||
12
src/routes/budget/[slug]/+page.server.js
Normal file
12
src/routes/budget/[slug]/+page.server.js
Normal 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 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
35
src/routes/budget/[slug]/+page.svelte
Normal file
35
src/routes/budget/[slug]/+page.svelte
Normal 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>
|
||||
14
src/routes/transcation/[slug]/+server.js
Normal file
14
src/routes/transcation/[slug]/+server.js
Normal 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'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
plugins: [ tailwindcss(), sveltekit(),]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user