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:
72
package-lock.json
generated
72
package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"@sveltejs/adapter-auto": "^6.0.0",
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"daisyui": "^5.0.43",
|
"daisyui": "^5.0.43",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
@ -1104,6 +1105,22 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/typography": {
|
||||||
|
"version": "0.5.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
||||||
|
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.castarray": "^4.4.0",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"postcss-selector-parser": "6.0.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/vite": {
|
"node_modules/@tailwindcss/vite": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
|
||||||
@ -1193,6 +1210,19 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cssesc": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"cssesc": "bin/cssesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.0.43",
|
"version": "5.0.43",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.43.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.43.tgz",
|
||||||
@ -1615,6 +1645,27 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.castarray": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.merge": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.17",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
@ -1751,6 +1802,20 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-selector-parser": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postgres": {
|
"node_modules/postgres": {
|
||||||
"version": "3.4.7",
|
"version": "3.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
|
||||||
@ -1958,6 +2023,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"@sveltejs/adapter-auto": "^6.0.0",
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"daisyui": "^5.0.43",
|
"daisyui": "^5.0.43",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
@import "tailwindcss";
|
@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
|
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() {
|
export async function createBudgetTable() {
|
||||||
return await db`
|
return await db`
|
||||||
create table if not exists budget (
|
create table if not exists budget (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
name text not null,
|
name text not null,
|
||||||
amount numeric(10,2) not null,
|
amount numeric(10,2) not null,
|
||||||
notes text
|
notes text,
|
||||||
|
delete boolean default false
|
||||||
)
|
)
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@ -113,6 +130,7 @@ export async function getBudgets() {
|
|||||||
budget.amount as amount,
|
budget.amount as amount,
|
||||||
budget.notes as notes
|
budget.notes as notes
|
||||||
from budget
|
from budget
|
||||||
|
WHERE budget.delete is false
|
||||||
`
|
`
|
||||||
if (!budgets) {
|
if (!budgets) {
|
||||||
await createBudgetTable();
|
await createBudgetTable();
|
||||||
@ -230,15 +248,15 @@ export async function updateAccounts(data) {
|
|||||||
}
|
}
|
||||||
console.log(`Preparing to upsert transaction: ${txn.id} with data:`, txn);
|
console.log(`Preparing to upsert transaction: ${txn.id} with data:`, txn);
|
||||||
await db`
|
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 (
|
values (
|
||||||
${txn.id},
|
${txn.id},
|
||||||
${account.id},
|
${account.id},
|
||||||
${txn.posted},
|
${txn.posted},
|
||||||
${txn.amount ?? null},
|
${txn.amount ?? null},
|
||||||
${txn.description ?? null},
|
${txn.description ?? null},
|
||||||
${txn.pending},
|
${txn.pending ?? false},
|
||||||
${extraId}
|
${txn.transacted_at ?? 0}
|
||||||
)
|
)
|
||||||
on conflict (id) do update set
|
on conflict (id) do update set
|
||||||
account_id = excluded.account_id,
|
account_id = excluded.account_id,
|
||||||
@ -246,7 +264,7 @@ export async function updateAccounts(data) {
|
|||||||
amount = excluded.amount,
|
amount = excluded.amount,
|
||||||
description = excluded.description,
|
description = excluded.description,
|
||||||
pending = excluded.pending,
|
pending = excluded.pending,
|
||||||
extra_id = excluded.extra_id
|
transacted_at = excluded.transacted_at
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<h1 class="text-2xl font-bold">Account: {data.account.name}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Transcations</h1>
|
<h1>Transcations</h1>
|
||||||
<table class="table w-full">
|
<table class="table w-full">
|
||||||
<thead>
|
<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";
|
const url = "https://19443E0E8171E175EC5DA0C69B35DD50197F234B9A74C00D27FD606121257ECF:DAA3702E2100CFFD3B544251E6D755E86B1EDDFBFCC7F6FA9CE77AB3677E60DE@beta-bridge.simplefin.org/simplefin";
|
||||||
|
|
||||||
export async function POST() {
|
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));
|
return new Response(await updateAccounts(res));
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
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)
|
// In-memory store for demonstration (replace with a database in production)
|
||||||
let budgets = [];
|
let budgets = [];
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { getBudgetTransactions } from '$lib/db.js';
|
|||||||
|
|
||||||
export async function load({ params }) {
|
export async function load({ params }) {
|
||||||
const { slug } = params;
|
const { slug } = params;
|
||||||
|
console.log(`Loading transactions for budget: ${slug}`);
|
||||||
const transactions = await getBudgetTransactions(slug);
|
const transactions = await getBudgetTransactions(slug);
|
||||||
|
|
||||||
return { transactions, slug };
|
return { transactions, slug };
|
||||||
|
|||||||
@ -1,12 +1,49 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let budget = $state(data.budgets.find((b) => b.id == data.slug) || {});
|
let budget = $state(data.budgets.find((b) => b.id == data.slug) || {});
|
||||||
let transactions = $state(data.transactions || []);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="align-text-top">
|
<div class="flex mb-4">
|
||||||
<h1 class="text-2xl font-bold">{budget.name}</h1>
|
<div class="w-32 flex-none justify-bottom"><h1 class="text-2xl font-bold">{budget.name}</h1></div>
|
||||||
<p class="text-lg">Amount: ${budget.amount}</p>
|
<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>
|
<p class="text-sm">Notes: {budget.notes}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -33,3 +70,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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