Add account management features and integrate Tailwind CSS typography

- Implement setAccountInTotal and deleteBudget functions in db.js
- Update budget handling in the budget server API
- Add DELETE endpoint for budget removal
- Enhance account page with delete functionality and confirmation modal
- Include @tailwindcss/typography in package.json and app.css
This commit is contained in:
2025-07-07 21:43:36 -04:00
parent 3bc6b9ab7c
commit 61d2a258fc
12 changed files with 233 additions and 45 deletions

View File

@ -1,2 +1,3 @@
@import "tailwindcss";
@plugin "daisyui";
@plugin "daisyui";
@plugin "@tailwindcss/typography";

View File

@ -3,13 +3,30 @@ 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 setAccountInTotal(accountId, total) {
return await db`
update account
set in_total = ${total}
where id = ${accountId}
`
}
export async function deleteBudget(id) {
return await db`
UPDATE budget
SET delete = true
WHERE id = ${id}
`
}
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
notes text,
delete boolean default false
)
`
}
@ -112,7 +129,8 @@ export async function getBudgets() {
budget.name as name,
budget.amount as amount,
budget.notes as notes
from budget
from budget
WHERE budget.delete is false
`
if (!budgets) {
await createBudgetTable();
@ -230,15 +248,15 @@ export async function updateAccounts(data) {
}
console.log(`Preparing to upsert transaction: ${txn.id} with data:`, txn);
await db`
insert into transaction (id, account_id, posted, amount, description, pending, extra_id)
insert into transaction (id, account_id, posted, amount, description, pending, transacted_at)
values (
${txn.id},
${account.id},
${txn.posted},
${txn.amount ?? null},
${txn.description ?? null},
${txn.pending},
${extraId}
${txn.pending ?? false},
${txn.transacted_at ?? 0}
)
on conflict (id) do update set
account_id = excluded.account_id,
@ -246,7 +264,7 @@ export async function updateAccounts(data) {
amount = excluded.amount,
description = excluded.description,
pending = excluded.pending,
extra_id = excluded.extra_id
transacted_at = excluded.transacted_at
`;
}
}

View File

@ -7,11 +7,11 @@
amount: 0,
notes: ''
});
async function update() {
let res = await fetch('/api/simplefin/update', {
method: 'POST'
});
}
async function update() {
let res = await fetch('/api/simplefin/update', {
method: 'POST'
});
}
async function saveBudget() {
let res = await fetch('/budget', {
method: 'POST',
@ -45,7 +45,7 @@
<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">
<li>
<button onclick={update}>Update</button>
<button onclick={update}>Update</button>
</li>
<li><div class="divider">Budgets</div></li>
{#each budgets as budget}

View File

@ -31,6 +31,10 @@
}
</script>
<div class="flex justify-between mb-4">
<h1 class="text-2xl font-bold">Account: {data.account.name}</h1>
</div>
<h1>Transcations</h1>
<table class="table w-full">
<thead>

View File

@ -0,0 +1,25 @@
import { setAccountInTotal } from "$lib/db";
export async function POST({ request }) {
const body = await request.json();
const { slug, total } = body;
console.log(`Setting account ${slug} in total to ${total}`);
try {
const res = await setAccountInTotal(slug, total);
return new Response(JSON.stringify({ success: true, data: res }), {
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
console.error('Error setting account in total:', error);
return new Response(JSON.stringify({ success: false, error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}
}

View File

@ -0,0 +1,11 @@
import { deleteBudget } from '$lib/db.js';
export function DELETE({ params }) {
const { slug } = params;
console.log(`Deleting budget with slug: ${slug}`);
// Call the deleteBudget function from db.js
return deleteBudget(slug)
.then(() => new Response(`Budget with slug ${slug} deleted successfully.`))
.catch(err => new Response(`Error deleting budget: ${err.message}`, { status: 500 }));
}

View File

@ -5,7 +5,6 @@ import { updateAccounts } from '$lib/db' ;
const url = "https://19443E0E8171E175EC5DA0C69B35DD50197F234B9A74C00D27FD606121257ECF:DAA3702E2100CFFD3B544251E6D755E86B1EDDFBFCC7F6FA9CE77AB3677E60DE@beta-bridge.simplefin.org/simplefin";
export async function POST() {
const res = await fetchAccounts(url, new Date("2025-07-01"))
const res = await fetchAccounts(url, new Date("2025-07-03"))
return new Response(await updateAccounts(res));
}

View File

@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit';
import { addBudget } from '$lib/db';
import { addBudget, deleteBudget } from '$lib/db';
// In-memory store for demonstration (replace with a database in production)
let budgets = [];
@ -15,4 +15,4 @@ export async function POST({ request }) {
let res = await addBudget(data.name, data.amount, data.notes);
return json(res, { status: 201 });
}
}

View File

@ -3,6 +3,7 @@ import { getBudgetTransactions } from '$lib/db.js';
export async function load({ params }) {
const { slug } = params;
console.log(`Loading transactions for budget: ${slug}`);
const transactions = await getBudgetTransactions(slug);
return { transactions, slug };

View File

@ -1,35 +1,91 @@
<script>
let { data } = $props();
let budget = $state(data.budgets.find((b) => b.id == data.slug) || {});
let transactions = $state(data.transactions || []);
let { data } = $props();
let budget = $state(data.budgets.find((b) => b.id == data.slug) || {});
let transactions = $state(data.transactions || []);
let toDeleteBudget = $state(null);
async function deleteBudget() {
if (toDeleteBudget === budget.name) {
let res = await fetch(`/api/budget/${budget.id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (res.ok) {
window.location.href = '/'; // Redirect to budgets list after deletion
} else {
console.error('Failed to delete budget');
}
} else {
alert('Please type the budget name correctly to confirm deletion.');
}
}
</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 class="flex mb-4">
<div class="w-32 flex-none justify-bottom"><h1 class="text-2xl font-bold">{budget.name}</h1></div>
<div class="w-64 grow">{budget.amount}</div>
<div class="w-14 flex-none text-right">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 30 30"
width="30px"
height="30px"
class="cursor-pointer hover:scale-110 transition"
onclick={()=> DeleteBudgetModal.showModal()}
>
<path d="M 14.984375 2.4863281 A 1.0001 1.0001 0 0 0 14 3.5 L 14 4 L 8.5 4 A 1.0001 1.0001 0 0 0 7.4863281 5 L 6 5 A 1.0001 1.0001 0 1 0 6 7 L 24 7 A 1.0001 1.0001 0 1 0 24 5 L 22.513672 5 A 1.0001 1.0001 0 0 0 21.5 4 L 16 4 L 16 3.5 A 1.0001 1.0001 0 0 0 14.984375 2.4863281 z M 6 9 L 7.7929688 24.234375 C 7.9109687 25.241375 8.7633438 26 9.7773438 26 L 20.222656 26 C 21.236656 26 22.088031 25.241375 22.207031 24.234375 L 24 9 L 6 9 z"/>
</svg>
</div>
</div>
<div class="mb-4">
<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>
<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>
<dialog id="DeleteBudgetModal" 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>
<p>Are you sure you want to delete <code>{budget.name}</code>? type its name in the box below if so.</p>
<div>
<input
bind:value={toDeleteBudget}
type="text"
placeholder="Budget Name"
class="input input-bordered w-full max-w-xs"
/>
</div>
<button onclick={()=>deleteBudget()} class="btn btn-primary mt-4">Delete</button>
</div>
</dialog>