From f31eb3dc0d5e224221ff2e39603fc3bcd7ab54af Mon Sep 17 00:00:00 2001 From: Norbert Maciaszek Date: Tue, 25 Nov 2025 00:16:21 +0100 Subject: [PATCH] Refactor year management in the database layer, enhance Year model with budget field, and update UI components for year selection and dashboard display --- .../migration.sql | 12 +++ prisma/schema.prisma | 3 +- src/lib/components/atoms/Modal.svelte | 31 ++++++ src/lib/components/molecules/GiftCard.svelte | 102 ++++++++++++++++++ src/lib/components/molecules/GiftModal.svelte | 84 +++++++++++++++ .../components/molecules/PersonCard.svelte | 55 ++++++++++ .../components/molecules/PersonModal.svelte | 44 ++++++++ .../components/organisms/YearDashboard.svelte | 80 ++++++++++++++ src/lib/helpers/formatCurrency.ts | 6 ++ src/lib/remotes/db.remote.ts | 58 +++++++++- src/lib/server/db.ts | 18 +++- src/routes/+layout.svelte | 5 +- src/routes/+page.svelte | 10 +- src/routes/layout.css | 2 +- svelte.config.js | 2 +- 15 files changed, 498 insertions(+), 14 deletions(-) create mode 100644 prisma/migrations/20251124231609_add_year_budget/migration.sql create mode 100644 src/lib/components/atoms/Modal.svelte create mode 100644 src/lib/components/molecules/GiftCard.svelte create mode 100644 src/lib/components/molecules/GiftModal.svelte create mode 100644 src/lib/components/molecules/PersonCard.svelte create mode 100644 src/lib/components/molecules/PersonModal.svelte create mode 100644 src/lib/components/organisms/YearDashboard.svelte create mode 100644 src/lib/helpers/formatCurrency.ts diff --git a/prisma/migrations/20251124231609_add_year_budget/migration.sql b/prisma/migrations/20251124231609_add_year_budget/migration.sql new file mode 100644 index 0000000..2aec976 --- /dev/null +++ b/prisma/migrations/20251124231609_add_year_budget/migration.sql @@ -0,0 +1,12 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Year" ( + "year" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "budget" REAL NOT NULL DEFAULT 3000 +); +INSERT INTO "new_Year" ("year") SELECT "year" FROM "Year"; +DROP TABLE "Year"; +ALTER TABLE "new_Year" RENAME TO "Year"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 746b4e3..1fc1006 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,8 @@ datasource db { } model Year { - year Int @id + year Int @id + budget Float @default(3000) people Person[] } diff --git a/src/lib/components/atoms/Modal.svelte b/src/lib/components/atoms/Modal.svelte new file mode 100644 index 0000000..741c4d0 --- /dev/null +++ b/src/lib/components/atoms/Modal.svelte @@ -0,0 +1,31 @@ + + + diff --git a/src/lib/components/molecules/GiftCard.svelte b/src/lib/components/molecules/GiftCard.svelte new file mode 100644 index 0000000..e294875 --- /dev/null +++ b/src/lib/components/molecules/GiftCard.svelte @@ -0,0 +1,102 @@ + + +
+
+
+ +
+

+ {gift.name} + {#if editing} + (editing = false)} /> + {:else} + (editing = true)} /> + {/if} +

+

{gift.description}

+ + {gift.url} + {#if !editing} + {#each gift.url?.split('\n') as line} + + {line} + + {/each} + {/if} +
+
+
+ {gift.price} + {#if !editing} + {formatCurrency(gift.price ?? 0)} + {/if} + deleteGift(gift.id)} /> +
+
+
diff --git a/src/lib/components/molecules/GiftModal.svelte b/src/lib/components/molecules/GiftModal.svelte new file mode 100644 index 0000000..53a0854 --- /dev/null +++ b/src/lib/components/molecules/GiftModal.svelte @@ -0,0 +1,84 @@ + + + + +{#if isOpen} + +
+ Nazwa + +
+
+ Cena + +
+
+ URL + +
+
+ Opis + +
+
+ Status + +
+
+ +
+
+{/if} diff --git a/src/lib/components/molecules/PersonCard.svelte b/src/lib/components/molecules/PersonCard.svelte new file mode 100644 index 0000000..a49a0ef --- /dev/null +++ b/src/lib/components/molecules/PersonCard.svelte @@ -0,0 +1,55 @@ + + +
+
(showGifts = !showGifts)} + > +
+
+ {person.name.charAt(0)} +
+
+

{person.name}

+
+ {person.gifts.length} prezentów • {person.gifts.filter((g) => g.status === 'BOUGHT') + .length}/{person.gifts.length} kupionych +
+
+
+
+ + {formatCurrency(person.gifts.reduce((sum, gift) => sum + (gift.price ?? 0), 0))} + + deletePerson(person.id)} /> +
+
+ + {#if showGifts} +
+ {#each gifts as gift (gift.id)} + + {/each} + + +
+ {/if} +
diff --git a/src/lib/components/molecules/PersonModal.svelte b/src/lib/components/molecules/PersonModal.svelte new file mode 100644 index 0000000..5da06c2 --- /dev/null +++ b/src/lib/components/molecules/PersonModal.svelte @@ -0,0 +1,44 @@ + + + + +{#if isOpen} + +
+ Nazwa + +
+
+ +
+
+{/if} diff --git a/src/lib/components/organisms/YearDashboard.svelte b/src/lib/components/organisms/YearDashboard.svelte new file mode 100644 index 0000000..e8ff1b8 --- /dev/null +++ b/src/lib/components/organisms/YearDashboard.svelte @@ -0,0 +1,80 @@ + + +
+ +
+
+
+

+ + Podsumowanie {year} +

+

+ Prezenty dla {data?.people?.length || 0} osób +

+
+
+
Łączny budżet
+
+ {formatCurrency(totalBudget)} +
+
+
+ +
+
+ Wydane: {formatCurrency(totalSpent)} + Zostało: {formatCurrency(remainingBudget)} +
+ +
+
+ + +
+
+

+ + Osoby +

+ +
+
+ {#each people as person (person.id)} + + {:else} +
+ +

Nie ma jeszcze żadnej osoby na liście

+

Dodaj osoby, aby móc śledzić ich prezenty!

+
+ {/each} +
+
+
diff --git a/src/lib/helpers/formatCurrency.ts b/src/lib/helpers/formatCurrency.ts new file mode 100644 index 0000000..72957a0 --- /dev/null +++ b/src/lib/helpers/formatCurrency.ts @@ -0,0 +1,6 @@ +export const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('pl-PL', { + style: 'currency', + currency: 'PLN' + }).format(amount); +}; diff --git a/src/lib/remotes/db.remote.ts b/src/lib/remotes/db.remote.ts index 013410f..cc3d8fa 100644 --- a/src/lib/remotes/db.remote.ts +++ b/src/lib/remotes/db.remote.ts @@ -2,8 +2,14 @@ import { command, query } from '$app/server'; import { DB } from '$lib/server/db'; import * as v from 'valibot'; +export const getYear = query(v.number(), async (year) => { + return await DB.GET.year({ + year: year + }); +}); + export const getYears = query(async () => { - return await DB.GET.years(); + return await DB.GET.years({}); }); export const addYear = command(v.number(), async (year) => { @@ -16,8 +22,56 @@ export const addYear = command(v.number(), async (year) => { export const deleteYear = command(v.number(), async (year) => { await DB.DELETE.year({ - year: year + year }); getYears().refresh(); }); + +export const addPerson = command(v.any(), async (data) => { + await DB.CREATE.person(data); + + getPeople(data.yearId).refresh(); +}); + +export const getPeople = query(v.number(), async (year) => { + return await DB.GET.people({ + yearId: year + }); +}); + +export const deletePerson = command(v.number(), async (id) => { + const person = await DB.DELETE.person({ id }); + const year = await DB.GET.year({ year: person!.yearId }); + + getPeople(year!.year).refresh(); +}); + +export const addGift = command(v.any(), async (data) => { + await DB.CREATE.gift({ + ...data, + person: { + connect: { + id: data.personId + } + } + }); + + const person = await DB.GET.person({ id: data.personId }); + + getPeople(person!.yearId).refresh(); +}); + +export const updateGift = command(v.any(), async (data) => { + const res = await DB.UPDATE.gift({ id: data.id }, data); + const person = await DB.GET.person({ id: res.personId }); + + getPeople(person!.yearId).refresh(); +}); + +export const deleteGift = command(v.number(), async (id) => { + const gift = await DB.DELETE.gift({ id }); + const person = await DB.GET.person({ id: gift.personId }); + + getPeople(person!.yearId).refresh(); +}); diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 5b2290b..b5485af 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -11,11 +11,16 @@ const db = new PrismaClient({ adapter }); const GET = { year: async (where: Prisma.YearWhereUniqueInput) => { return await db.year.findUnique({ - where + where, + include: { + people: true + } }); }, - years: async () => { - return await db.year.findMany(); + years: async (where: Prisma.YearWhereInput) => { + return await db.year.findMany({ + where + }); }, person: async (where: Prisma.PersonWhereUniqueInput) => { @@ -23,9 +28,12 @@ const GET = { where }); }, - people: async (where: Prisma.PersonWhereUniqueInput) => { + people: async (where: Prisma.PersonWhereInput) => { return await db.person.findMany({ - where + where, + include: { + gifts: true + } }); }, diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3e39c7f..f520e54 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,8 +11,9 @@ -
-
+
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..61ec622 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,8 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + + diff --git a/src/routes/layout.css b/src/routes/layout.css index ef837b1..32c9525 100644 --- a/src/routes/layout.css +++ b/src/routes/layout.css @@ -12,6 +12,6 @@ } .container { - @apply max-w-7xl mx-auto; + @apply max-w-4xl mx-auto; padding-inline: 15px; } diff --git a/svelte.config.js b/svelte.config.js index 97f4b6a..be5c9dd 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -20,7 +20,7 @@ const config = { compilerOptions: { warningFilter: (warning) => { - return !warning.message.startsWith('a11y_'); + return warning.message.startsWith('a11y_'); }, experimental: { async: true