diff --git a/src/lib/components/PaymentModal.svelte b/src/lib/components/PaymentModal.svelte index 1830113..f8a8056 100644 --- a/src/lib/components/PaymentModal.svelte +++ b/src/lib/components/PaymentModal.svelte @@ -3,6 +3,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import ProfilePicture from './ProfilePicture.svelte'; + import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories'; export let paymentId; @@ -104,7 +105,10 @@
-

{payment.title}

+
+ {getCategoryEmoji(payment.category || 'groceries')} +

{payment.title}

+
{formatCurrency(payment.amount)}
@@ -130,6 +134,10 @@ Created by: {payment.createdBy}
+
+ Category: + {getCategoryName(payment.category || 'groceries')} +
Split method: {getSplitDescription(payment)} @@ -255,8 +263,20 @@ border-bottom: 1px solid #dee2e6; } + .title-with-category { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.5rem; + } + + .title-with-category .category-emoji { + font-size: 1.8rem; + flex-shrink: 0; + } + .title-section h1 { - margin: 0 0 0.5rem 0; + margin: 0; color: #333; font-size: 1.5rem; } diff --git a/src/lib/models/Payment.ts b/src/lib/models/Payment.ts deleted file mode 100644 index 9632b4a..0000000 --- a/src/lib/models/Payment.ts +++ /dev/null @@ -1,14 +0,0 @@ -import mongoose from 'mongoose'; - -const paymentSchema = new mongoose.Schema({ - paid_by: { type: String, required: true }, - total_amount: { type: Number, required: true }, - for_self: { type: Number, default: 0 }, - for_other: { type: Number, default: 0 }, - currency: { type: String, default: 'CHF' }, - description: String, - date: { type: Date, default: Date.now }, - receipt_image: String -}); - -export const Payment = mongoose.models.Payment || mongoose.model('Payment', paymentSchema); \ No newline at end of file diff --git a/src/lib/utils/categories.ts b/src/lib/utils/categories.ts new file mode 100644 index 0000000..6da72b7 --- /dev/null +++ b/src/lib/utils/categories.ts @@ -0,0 +1,49 @@ +export const PAYMENT_CATEGORIES = { + groceries: { + name: 'Groceries', + emoji: '🛒' + }, + shopping: { + name: 'Shopping', + emoji: '🛍️' + }, + travel: { + name: 'Travel', + emoji: '🚆' + }, + restaurant: { + name: 'Restaurant', + emoji: '🍽️' + }, + utilities: { + name: 'Utilities', + emoji: '⚡' + }, + fun: { + name: 'Fun', + emoji: '🎉' + } +} as const; + +export type PaymentCategory = keyof typeof PAYMENT_CATEGORIES; + +export function getCategoryInfo(category: PaymentCategory) { + return PAYMENT_CATEGORIES[category] || PAYMENT_CATEGORIES.groceries; +} + +export function getCategoryEmoji(category: PaymentCategory) { + return getCategoryInfo(category).emoji; +} + +export function getCategoryName(category: PaymentCategory) { + return getCategoryInfo(category).name; +} + +export function getCategoryOptions() { + return Object.entries(PAYMENT_CATEGORIES).map(([key, value]) => ({ + value: key as PaymentCategory, + label: `${value.emoji} ${value.name}`, + emoji: value.emoji, + name: value.name + })); +} \ No newline at end of file diff --git a/src/models/Payment.ts b/src/models/Payment.ts index 197a727..cc47cd0 100644 --- a/src/models/Payment.ts +++ b/src/models/Payment.ts @@ -9,6 +9,7 @@ export interface IPayment { paidBy: string; // username/nickname of the person who paid date: Date; image?: string; // path to uploaded image + category: 'groceries' | 'shopping' | 'travel' | 'restaurant' | 'utilities' | 'fun'; splitMethod: 'equal' | 'full' | 'proportional'; createdBy: string; // username/nickname of the person who created the payment createdAt?: Date; @@ -51,6 +52,12 @@ const PaymentSchema = new mongoose.Schema( type: String, trim: true }, + category: { + type: String, + required: true, + enum: ['groceries', 'shopping', 'travel', 'restaurant', 'utilities', 'fun'], + default: 'groceries' + }, splitMethod: { type: String, required: true, diff --git a/src/routes/api/cospend/payments/+server.ts b/src/routes/api/cospend/payments/+server.ts index 82272e0..7e5fa3d 100644 --- a/src/routes/api/cospend/payments/+server.ts +++ b/src/routes/api/cospend/payments/+server.ts @@ -38,7 +38,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { } const data = await request.json(); - const { title, description, amount, paidBy, date, image, splitMethod, splits } = data; + const { title, description, amount, paidBy, date, image, category, splitMethod, splits } = data; if (!title || !amount || !paidBy || !splitMethod || !splits) { throw error(400, 'Missing required fields'); @@ -52,6 +52,10 @@ export const POST: RequestHandler = async ({ request, locals }) => { throw error(400, 'Invalid split method'); } + if (category && !['groceries', 'shopping', 'travel', 'restaurant', 'utilities', 'fun'].includes(category)) { + throw error(400, 'Invalid category'); + } + await dbConnect(); try { @@ -63,6 +67,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { paidBy, date: date ? new Date(date) : new Date(), image, + category: category || 'groceries', splitMethod, createdBy: auth.user.nickname }); diff --git a/src/routes/api/cospend/payments/[id]/+server.ts b/src/routes/api/cospend/payments/[id]/+server.ts index 45d8296..a6752c8 100644 --- a/src/routes/api/cospend/payments/[id]/+server.ts +++ b/src/routes/api/cospend/payments/[id]/+server.ts @@ -61,6 +61,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => { paidBy: data.paidBy, date: data.date ? new Date(data.date) : payment.date, image: data.image, + category: data.category || payment.category, splitMethod: data.splitMethod }, { new: true } diff --git a/src/routes/cospend/+page.svelte b/src/routes/cospend/+page.svelte index b736695..d4201c5 100644 --- a/src/routes/cospend/+page.svelte +++ b/src/routes/cospend/+page.svelte @@ -3,6 +3,7 @@ import { page } from '$app/stores'; import { pushState } from '$app/navigation'; import ProfilePicture from '$lib/components/ProfilePicture.svelte'; + import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories'; export let data; // Used by the layout for session data @@ -109,8 +110,12 @@ >
0}> {#if split.amount > 0} @@ -368,6 +373,23 @@ gap: 0.25rem; } + .payment-title-row { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .category-emoji { + font-size: 1.2rem; + flex-shrink: 0; + } + + .category-name { + color: #888; + font-size: 0.8rem; + font-style: italic; + } + .payment-title { color: #333; font-size: 1.1rem; diff --git a/src/routes/cospend/payments/+page.svelte b/src/routes/cospend/payments/+page.svelte index e4c5b8f..d012aa3 100644 --- a/src/routes/cospend/payments/+page.svelte +++ b/src/routes/cospend/payments/+page.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import ProfilePicture from '$lib/components/ProfilePicture.svelte'; + import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories'; export let data; @@ -137,8 +138,12 @@
-

{payment.title}

+
+ {getCategoryEmoji(payment.category || 'groceries')} +

{payment.title}

+
+ {getCategoryName(payment.category || 'groceries')} {formatDate(payment.date)} {formatCurrency(payment.amount)}
@@ -346,8 +351,20 @@ flex: 1; } + .title-with-category { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; + } + + .title-with-category .category-emoji { + font-size: 1.3rem; + flex-shrink: 0; + } + .payment-title h3 { - margin: 0 0 0.5rem 0; + margin: 0; color: #333; font-size: 1.25rem; } @@ -357,6 +374,13 @@ gap: 1rem; font-size: 0.9rem; color: #666; + flex-wrap: wrap; + } + + .payment-meta .category-name { + color: #888; + font-style: italic; + font-size: 0.8rem; } .payment-meta .amount { diff --git a/src/routes/cospend/payments/add/+page.svelte b/src/routes/cospend/payments/add/+page.svelte index 46a0de2..96fc6eb 100644 --- a/src/routes/cospend/payments/add/+page.svelte +++ b/src/routes/cospend/payments/add/+page.svelte @@ -1,6 +1,7 @@