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:
@ -1,2 +1,3 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
@plugin "daisyui";
|
||||
@plugin "@tailwindcss/typography";
|
||||
@ -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
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
25
src/routes/api/account/[slug]/+server.js
Normal file
25
src/routes/api/account/[slug]/+server.js
Normal 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'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
11
src/routes/api/budget/[slug]/+server.js
Normal file
11
src/routes/api/budget/[slug]/+server.js
Normal 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 }));
|
||||
}
|
||||
@ -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));
|
||||
|
||||
}
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user