diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index a731a31b0..a9a7720d2 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -563,6 +563,17 @@ db.version(23).stores({ userContext: 'id', }); +// v24 — Wishes module: wishlists with price tracking. +// wishesItems indexes [listId+order] for the per-list view, +// status for the active/fulfilled filter tabs. +// wishesPriceChecks indexes [wishId+checkedAt] for the per-wish +// price history timeline (reverse range scan). +db.version(24).stores({ + wishesItems: 'id, listId, status, priority, category, [listId+order], [status+order]', + wishesLists: 'id, order, isArchived', + wishesPriceChecks: 'id, wishId, checkedAt, [wishId+checkedAt]', +}); + // v25 — Wetter module: saved locations and user preferences. db.version(25).stores({ wetterLocations: 'id, isDefault, order', diff --git a/apps/mana/apps/web/src/lib/data/seed-registry.ts b/apps/mana/apps/web/src/lib/data/seed-registry.ts index 88101b892..56da92c86 100644 --- a/apps/mana/apps/web/src/lib/data/seed-registry.ts +++ b/apps/mana/apps/web/src/lib/data/seed-registry.ts @@ -35,6 +35,7 @@ import { MEDITATE_GUEST_SEED } from '$lib/modules/meditate/collections'; import { SLEEP_GUEST_SEED } from '$lib/modules/sleep/collections'; import { MOOD_GUEST_SEED } from '$lib/modules/mood/collections'; import { QUIZ_GUEST_SEED } from '$lib/modules/quiz/collections'; +import { WISHES_GUEST_SEED } from '$lib/modules/wishes/collections'; /** * Flat list of { tableName, rows } entries. Only modules with non-empty @@ -74,6 +75,7 @@ register(MEDITATE_GUEST_SEED); register(SLEEP_GUEST_SEED); register(MOOD_GUEST_SEED); register(QUIZ_GUEST_SEED); +register(WISHES_GUEST_SEED); /** * Seed all module guest data into empty tables. Idempotent: tables diff --git a/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte b/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte new file mode 100644 index 000000000..ab784cb22 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte @@ -0,0 +1,341 @@ + + + + Wünsche - Mana + + +
+ +
+

+ {activeCount} offen · {fulfilledCount} erfüllt + {#if totalCost > 0} + · ~{totalCost.toLocaleString('de-DE')} € + {/if} +

+ +
+ + + {#if showAdd} +
{ + e.preventDefault(); + addWish(); + }} + class="rounded-lg border border-[hsl(var(--color-border))] bg-[hsl(var(--color-card))] p-3" + > +
+ +
+ + + {#if lists.length > 0} + + {/if} +
+
+ + +
+
+
+ {/if} + + +
+ {#each [{ key: 'active', label: 'Offen', icon: Star }, { key: 'fulfilled', label: 'Erfüllt', icon: Check }, { key: 'all', label: 'Alle', icon: Archive }] as tab (tab.key)} + + {/each} +
+ + +
+ {#if lists.length > 0} + + {/if} + {#each lists as list (list.id)} +
+ + +
+ {/each} + {#if showNewList} +
{ + e.preventDefault(); + addList(); + }} + class="flex items-center gap-1" + > + + + +
+ {:else} + + {/if} +
+ + +
+ + +
+ + + {#if filtered.length === 0} +
+ +

+ {filter === 'active' ? 'Noch keine Wünsche' : 'Keine Wünsche in dieser Ansicht'} +

+
+ {:else} +
+ {#each filtered as wish (wish.id)} + + {/each} +
+ {/if} +
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/collections.ts b/apps/mana/apps/web/src/lib/modules/wishes/collections.ts new file mode 100644 index 000000000..3154e9c71 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/collections.ts @@ -0,0 +1,67 @@ +/** + * Wishes module — collection accessors and guest seed data. + * + * Uses prefixed table names in the unified DB: wishesItems, wishesLists, wishesPriceChecks. + */ + +import { db } from '$lib/data/database'; +import type { LocalWish, LocalWishList, LocalPriceCheck } from './types'; + +// ─── Collection Accessors ────────────────────────────────── + +export const wishTable = db.table('wishesItems'); +export const listTable = db.table('wishesLists'); +export const priceCheckTable = db.table('wishesPriceChecks'); + +// ─── Guest Seed ──────────────────────────────────────────── + +const DEMO_LIST_ID = 'demo-birthday-wishes'; + +export const WISHES_GUEST_SEED = { + wishesLists: [ + { + id: DEMO_LIST_ID, + name: 'Geburtstagsgeschenke', + description: 'Ideen für Geburtstag', + icon: '🎁', + color: '#ec4899', + isArchived: false, + order: 0, + }, + ], + wishesItems: [ + { + id: 'demo-wish-1', + title: 'Neue Kopfhörer', + description: 'Wireless, aktive Geräuschunterdrückung', + listId: DEMO_LIST_ID, + priority: 'medium' as const, + status: 'active' as const, + targetPrice: 150, + currency: 'EUR', + productUrls: [], + imageUrl: null, + category: 'Technik', + tags: ['audio', 'tech'], + notes: [], + order: 0, + }, + { + id: 'demo-wish-2', + title: 'Kochbuch — vegetarische Rezepte', + description: 'Am liebsten asiatisch-vegetarisch', + listId: DEMO_LIST_ID, + priority: 'low' as const, + status: 'active' as const, + targetPrice: 25, + currency: 'EUR', + productUrls: [], + imageUrl: null, + category: 'Bücher', + tags: ['books', 'cooking'], + notes: [], + order: 1, + }, + ], + wishesPriceChecks: [] as Record[], +}; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/index.ts b/apps/mana/apps/web/src/lib/modules/wishes/index.ts new file mode 100644 index 000000000..f4d1a75e8 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/index.ts @@ -0,0 +1,38 @@ +/** + * Wishes module — barrel exports. + */ + +// Stores +export { wishesStore } from './stores/wishes.svelte'; +export { listsStore } from './stores/lists.svelte'; +export { priceChecksStore } from './stores/price-checks.svelte'; + +// Queries +export { + useAllWishes, + useAllLists, + usePriceChecks, + toWish, + toWishList, + toPriceCheck, + filterByStatus, + filterByPriority, + filterByList, + searchWishes, + getTotalEstimatedCost, +} from './queries'; + +// Collections +export { wishTable, listTable, priceCheckTable, WISHES_GUEST_SEED } from './collections'; + +// Types +export type { + LocalWish, + LocalWishList, + LocalPriceCheck, + Wish, + WishList, + PriceCheck, + WishStatus, + WishPriority, +} from './types'; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts b/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts new file mode 100644 index 000000000..18ca22346 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts @@ -0,0 +1,10 @@ +import type { ModuleConfig } from '$lib/data/module-registry'; + +export const wishesModuleConfig: ModuleConfig = { + appId: 'wishes', + tables: [ + { name: 'wishesItems', syncName: 'items' }, + { name: 'wishesLists', syncName: 'lists' }, + { name: 'wishesPriceChecks', syncName: 'priceChecks' }, + ], +}; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/queries.ts b/apps/mana/apps/web/src/lib/modules/wishes/queries.ts new file mode 100644 index 000000000..6bcd667f3 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/queries.ts @@ -0,0 +1,129 @@ +/** + * Reactive queries & pure helpers for Wishes — uses Dexie liveQuery on the unified DB. + */ + +import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; +import { db } from '$lib/data/database'; +import { decryptRecords } from '$lib/data/crypto'; +import type { + LocalWish, + LocalWishList, + LocalPriceCheck, + Wish, + WishList, + PriceCheck, +} from './types'; + +// ─── Type Converters ─────────────────────────────────────── + +export function toWish(local: LocalWish): Wish { + return { + id: local.id, + title: local.title, + description: local.description ?? null, + listId: local.listId ?? null, + priority: local.priority, + targetPrice: local.targetPrice ?? null, + currency: local.currency ?? null, + productUrls: local.productUrls ?? [], + imageUrl: local.imageUrl ?? null, + category: local.category ?? null, + status: local.status, + tags: local.tags ?? [], + notes: local.notes ?? [], + order: local.order, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toWishList(local: LocalWishList): WishList { + return { + id: local.id, + name: local.name, + description: local.description ?? null, + icon: local.icon ?? null, + color: local.color ?? null, + isArchived: local.isArchived, + order: local.order, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toPriceCheck(local: LocalPriceCheck): PriceCheck { + return { + id: local.id, + wishId: local.wishId, + url: local.url, + price: local.price, + currency: local.currency, + available: local.available, + checkedAt: local.checkedAt, + createdAt: local.createdAt ?? new Date().toISOString(), + }; +} + +// ─── Live Queries ────────────────────────────────────────── + +export function useAllWishes() { + return useLiveQueryWithDefault(async () => { + const locals = await db.table('wishesItems').orderBy('order').toArray(); + const visible = locals.filter((w) => !w.deletedAt); + const decrypted = await decryptRecords('wishesItems', visible); + return decrypted.map(toWish); + }, [] as Wish[]); +} + +export function useAllLists() { + return useLiveQueryWithDefault(async () => { + const locals = await db.table('wishesLists').orderBy('order').toArray(); + const visible = locals.filter((l) => !l.deletedAt && !l.isArchived); + return visible.map(toWishList); + }, [] as WishList[]); +} + +export function usePriceChecks(wishId: string) { + return useLiveQueryWithDefault(async () => { + const locals = await db + .table('wishesPriceChecks') + .where('wishId') + .equals(wishId) + .toArray(); + const visible = locals.filter((p) => !p.deletedAt); + return visible + .sort((a, b) => new Date(b.checkedAt).getTime() - new Date(a.checkedAt).getTime()) + .map(toPriceCheck); + }, [] as PriceCheck[]); +} + +// ─── Pure Filter Functions ──────────────────────────────── + +export function filterByStatus(wishes: Wish[], status: string): Wish[] { + return wishes.filter((w) => w.status === status); +} + +export function filterByPriority(wishes: Wish[], priority: string): Wish[] { + return wishes.filter((w) => w.priority === priority); +} + +export function filterByList(wishes: Wish[], listId: string | null): Wish[] { + if (listId === null) return wishes.filter((w) => !w.listId); + return wishes.filter((w) => w.listId === listId); +} + +export function searchWishes(wishes: Wish[], query: string): Wish[] { + if (!query.trim()) return wishes; + const q = query.toLowerCase().trim(); + return wishes.filter( + (w) => + w.title.toLowerCase().includes(q) || + w.description?.toLowerCase().includes(q) || + w.category?.toLowerCase().includes(q) || + w.tags.some((t) => t.toLowerCase().includes(q)) + ); +} + +export function getTotalEstimatedCost(wishes: Wish[]): number { + return wishes.reduce((sum, w) => sum + (w.targetPrice ?? 0), 0); +} diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts new file mode 100644 index 000000000..e98fc1943 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts @@ -0,0 +1,57 @@ +/** + * Wish Lists Store — Mutations Only + */ + +import { listTable } from '../collections'; +import { toWishList } from '../queries'; +import type { LocalWishList } from '../types'; + +export const listsStore = { + async create(data: { name: string; description?: string; icon?: string; color?: string }) { + const all = await listTable.toArray(); + const active = all.filter((l) => !l.deletedAt); + + const newLocal: LocalWishList = { + id: crypto.randomUUID(), + name: data.name, + description: data.description ?? null, + icon: data.icon ?? null, + color: data.color ?? null, + isArchived: false, + order: active.length, + }; + await listTable.add(newLocal); + return toWishList(newLocal); + }, + + async update( + id: string, + data: Partial> + ) { + await listTable.update(id, { + ...data, + updatedAt: new Date().toISOString(), + }); + }, + + async archive(id: string) { + await listTable.update(id, { + isArchived: true, + updatedAt: new Date().toISOString(), + }); + }, + + async delete(id: string) { + await listTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async reorder(orderedIds: string[]) { + const now = new Date().toISOString(); + for (let i = 0; i < orderedIds.length; i++) { + await listTable.update(orderedIds[i], { order: i, updatedAt: now }); + } + }, +}; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts new file mode 100644 index 000000000..707e2a822 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts @@ -0,0 +1,29 @@ +/** + * Price Checks Store — Mutations Only + */ + +import { priceCheckTable } from '../collections'; +import type { LocalPriceCheck } from '../types'; + +export const priceChecksStore = { + async record(data: { + wishId: string; + url: string; + price: number; + currency: string; + available?: boolean; + }) { + const now = new Date().toISOString(); + const newLocal: LocalPriceCheck = { + id: crypto.randomUUID(), + wishId: data.wishId, + url: data.url, + price: data.price, + currency: data.currency, + available: data.available ?? true, + checkedAt: now, + }; + await priceCheckTable.add(newLocal); + return newLocal; + }, +}; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts new file mode 100644 index 000000000..02f8a9e5f --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts @@ -0,0 +1,142 @@ +/** + * Wishes Store — Mutations Only + * + * All reads are handled by liveQuery hooks in queries.ts. + */ + +import { wishTable } from '../collections'; +import { toWish } from '../queries'; +import type { LocalWish, WishPriority } from '../types'; +import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; + +export const wishesStore = { + async create(data: { + title: string; + description?: string; + listId?: string | null; + priority?: WishPriority; + targetPrice?: number; + currency?: string; + productUrls?: string[]; + imageUrl?: string; + category?: string; + tags?: string[]; + }) { + const existing = await wishTable.toArray(); + const active = existing.filter((w) => !w.deletedAt); + + const newLocal: LocalWish = { + id: crypto.randomUUID(), + title: data.title, + description: data.description ?? null, + listId: data.listId ?? null, + priority: data.priority ?? 'medium', + targetPrice: data.targetPrice ?? null, + currency: data.currency ?? 'EUR', + productUrls: data.productUrls ?? [], + imageUrl: data.imageUrl ?? null, + category: data.category ?? null, + status: 'active', + tags: data.tags ?? [], + notes: [], + order: active.length, + }; + + const plaintextSnapshot = toWish(newLocal); + await encryptRecord('wishesItems', newLocal); + await wishTable.add(newLocal); + emitDomainEvent('WishCreated', 'wishes', 'wishesItems', newLocal.id, { + wishId: newLocal.id, + title: data.title, + listId: data.listId, + }); + return plaintextSnapshot; + }, + + async update( + id: string, + data: Partial< + Pick< + LocalWish, + | 'title' + | 'description' + | 'priority' + | 'status' + | 'targetPrice' + | 'currency' + | 'productUrls' + | 'imageUrl' + | 'category' + | 'tags' + | 'listId' + > + > + ) { + const diff: Partial = { + ...data, + updatedAt: new Date().toISOString(), + }; + await encryptRecord('wishesItems', diff); + await wishTable.update(id, diff); + }, + + async fulfill(id: string) { + await wishTable.update(id, { + status: 'fulfilled', + updatedAt: new Date().toISOString(), + }); + emitDomainEvent('WishFulfilled', 'wishes', 'wishesItems', id, { wishId: id }); + }, + + async archive(id: string) { + await wishTable.update(id, { + status: 'archived', + updatedAt: new Date().toISOString(), + }); + }, + + async delete(id: string) { + await wishTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async addNote(wishId: string, content: string) { + const wish = await wishTable.get(wishId); + if (!wish) return; + const now = new Date().toISOString(); + const note = { id: crypto.randomUUID(), content, createdAt: now }; + await wishTable.update(wishId, { + notes: [...wish.notes, note], + updatedAt: now, + }); + }, + + async addProductUrl(wishId: string, url: string) { + const wish = await wishTable.get(wishId); + if (!wish) return; + if (wish.productUrls.includes(url)) return; + await wishTable.update(wishId, { + productUrls: [...wish.productUrls, url], + updatedAt: new Date().toISOString(), + }); + }, + + async removeProductUrl(wishId: string, url: string) { + const wish = await wishTable.get(wishId); + if (!wish) return; + await wishTable.update(wishId, { + productUrls: wish.productUrls.filter((u) => u !== url), + updatedAt: new Date().toISOString(), + }); + }, + + async reorder(orderedIds: string[]) { + const now = new Date().toISOString(); + for (let i = 0; i < orderedIds.length; i++) { + await wishTable.update(orderedIds[i], { order: i, updatedAt: now }); + } + }, +}; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/tools.ts b/apps/mana/apps/web/src/lib/modules/wishes/tools.ts new file mode 100644 index 000000000..7c5b24378 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/tools.ts @@ -0,0 +1,109 @@ +/** + * Wishes Tools — LLM-accessible operations for the wishes module. + */ + +import type { ModuleTool } from '$lib/data/tools/types'; + +export const wishesTools: ModuleTool[] = [ + { + name: 'create_wish', + module: 'wishes', + description: + 'Erstellt einen neuen Wunsch auf der Wunschliste. Nutze dies wenn der Nutzer sich etwas wünscht oder eine Geschenkidee hat.', + parameters: [ + { name: 'title', type: 'string', description: 'Wunsch-Titel', required: true }, + { name: 'description', type: 'string', description: 'Beschreibung', required: false }, + { + name: 'priority', + type: 'string', + description: 'Priorität', + required: false, + enum: ['low', 'medium', 'high'], + }, + { + name: 'targetPrice', + type: 'number', + description: 'Zielpreis / Budget in EUR', + required: false, + }, + { + name: 'category', + type: 'string', + description: 'Kategorie (z.B. Technik, Bücher)', + required: false, + }, + ], + async execute(params) { + const { wishesStore } = await import('./stores/wishes.svelte'); + const wish = await wishesStore.create({ + title: params.title as string, + description: params.description as string | undefined, + priority: (params.priority as 'low' | 'medium' | 'high') ?? undefined, + targetPrice: params.targetPrice as number | undefined, + category: params.category as string | undefined, + }); + return { success: true, data: wish, message: `Wunsch "${wish.title}" erstellt` }; + }, + }, + { + name: 'list_wishes', + module: 'wishes', + description: + 'Listet alle Wünsche auf der Wunschliste. Nutze dies wenn der Nutzer nach seinen Wünschen fragt.', + parameters: [ + { + name: 'filter', + type: 'string', + description: 'Nach Status filtern', + required: false, + enum: ['active', 'fulfilled', 'all'], + }, + ], + async execute(params) { + const { wishTable } = await import('./collections'); + const { toWish } = await import('./queries'); + const { decryptRecords } = await import('$lib/data/crypto'); + const all = await wishTable.toArray(); + const active = all.filter((w) => !w.deletedAt); + const decrypted = await decryptRecords('wishesItems', active); + const wishes = decrypted.map(toWish); + + const filter = (params.filter as string) ?? 'active'; + let filtered = wishes; + if (filter === 'active') filtered = wishes.filter((w) => w.status === 'active'); + else if (filter === 'fulfilled') filtered = wishes.filter((w) => w.status === 'fulfilled'); + + const list = filtered.map((w) => ({ + id: w.id, + title: w.title, + priority: w.priority, + targetPrice: w.targetPrice, + currency: w.currency, + category: w.category, + status: w.status, + })); + + return { + success: true, + data: list, + message: + list.length === 0 + ? `Keine ${filter === 'all' ? '' : filter + 'n'} Wünsche` + : `${list.length} Wünsche gefunden`, + }; + }, + }, + { + name: 'fulfill_wish', + module: 'wishes', + description: 'Markiert einen Wunsch als erfüllt.', + parameters: [ + { name: 'wishId', type: 'string', description: 'ID des Wunsches', required: true }, + ], + async execute(params) { + const { wishesStore } = await import('./stores/wishes.svelte'); + await wishesStore.fulfill(params.wishId as string); + return { success: true, message: 'Wunsch als erfüllt markiert' }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/types.ts b/apps/mana/apps/web/src/lib/modules/wishes/types.ts new file mode 100644 index 000000000..f0230b15b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/types.ts @@ -0,0 +1,86 @@ +/** + * Wishes module types for the unified app. + */ + +import type { BaseRecord } from '@mana/local-store'; + +export interface LocalWish extends BaseRecord { + title: string; + description?: string | null; + listId?: string | null; + priority: 'low' | 'medium' | 'high'; + targetPrice?: number | null; + currency?: string | null; + productUrls: string[]; + imageUrl?: string | null; + category?: string | null; + status: 'active' | 'fulfilled' | 'archived'; + tags: string[]; + notes: Array<{ id: string; content: string; createdAt: string }>; + order: number; +} + +export interface LocalWishList extends BaseRecord { + name: string; + description?: string | null; + icon?: string | null; + color?: string | null; + isArchived: boolean; + order: number; +} + +export interface LocalPriceCheck extends BaseRecord { + wishId: string; + url: string; + price: number; + currency: string; + available: boolean; + checkedAt: string; +} + +// ─── Public Types (post-decryption, used in UI) ─────────── + +export interface Wish { + id: string; + title: string; + description?: string | null; + listId?: string | null; + priority: 'low' | 'medium' | 'high'; + targetPrice?: number | null; + currency?: string | null; + productUrls: string[]; + imageUrl?: string | null; + category?: string | null; + status: 'active' | 'fulfilled' | 'archived'; + tags: string[]; + notes: Array<{ id: string; content: string; createdAt: string }>; + order: number; + createdAt: string; + updatedAt: string; +} + +export interface WishList { + id: string; + name: string; + description?: string | null; + icon?: string | null; + color?: string | null; + isArchived: boolean; + order: number; + createdAt: string; + updatedAt: string; +} + +export interface PriceCheck { + id: string; + wishId: string; + url: string; + price: number; + currency: string; + available: boolean; + checkedAt: string; + createdAt: string; +} + +export type WishStatus = Wish['status']; +export type WishPriority = Wish['priority']; diff --git a/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte new file mode 100644 index 000000000..cb0ea646c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte @@ -0,0 +1,373 @@ + + + + {wish?.title ?? 'Wunsch'} - Wünsche - Mana + + +{#if !wish} +
+

Wunsch nicht gefunden

+
+{:else} +
+ +
+ +
+ {#if wish.status === 'active'} + + {/if} + +
+
+ + + {#if editing} +
+ + +
+ + +
+
+ + +
+
+ {:else} + + {/if} + + +
+

+ + Produkt-Links ({wish.productUrls.length}) +

+ + {#if wish.productUrls.length > 0} +
    + {#each wish.productUrls as url} +
  • + + {url} + + +
  • + {/each} +
+ {/if} + +
{ + e.preventDefault(); + addUrl(); + }} + class="flex gap-2" + > + + +
+
+ + + {#if priceChecks.value.length > 0} +
+

Preisverlauf

+
+ {#each priceChecks.value.slice(0, 10) as check (check.id)} +
+ + {new Date(check.checkedAt).toLocaleDateString('de-DE')} + + + {check.price.toLocaleString('de-DE')} + {check.currency} + +
+ {/each} +
+
+ {/if} + + +
+

+ Notizen ({wish.notes.length}) +

+ + {#if wish.notes.length > 0} +
    + {#each wish.notes as note (note.id)} +
  • +

    {note.content}

    +

    + {new Date(note.createdAt).toLocaleString('de-DE')} +

    +
  • + {/each} +
+ {/if} + +
{ + e.preventDefault(); + addNote(); + }} + class="flex gap-2" + > + + +
+
+ + + {#if wish.tags.length > 0} +
+ {#each wish.tags as tag} + + {tag} + + {/each} +
+ {/if} +
+{/if} diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte new file mode 100644 index 000000000..ae9c9d035 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte new file mode 100644 index 000000000..7707c1aac --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte new file mode 100644 index 000000000..7f6ecdb83 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/shared-ui/src/dnd/types.ts b/packages/shared-ui/src/dnd/types.ts index d4be73af4..7a1a4b8e2 100644 --- a/packages/shared-ui/src/dnd/types.ts +++ b/packages/shared-ui/src/dnd/types.ts @@ -26,7 +26,8 @@ export type DragType = | 'place' | 'dream' | 'journal-entry' - | 'first'; + | 'first' + | 'wish'; export interface DragPayload> { type: DragType;