From 96a7202daa63ce65d8009b6f5ff9b7c923906e43 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 17 Apr 2026 13:33:59 +0200 Subject: [PATCH] feat(wetter): register workbench panel with ListView Add ListView.svelte (compact weather card for workbench panels), register via registerApp() in apps.ts with CloudSun icon, and assign to 'life' category so it appears in the Leben section of the AppPagePicker. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/app-registry/apps.ts | 51 ++++ .../web/src/lib/app-registry/categories.ts | 1 + .../src/lib/modules/wetter/ListView.svelte | 289 ++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/modules/wetter/ListView.svelte diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index b2ff7d44b..60762806f 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -71,6 +71,8 @@ import { Scroll, Spiral, Crown, + ShootingStar, + CloudSun, } from '@mana/shared-icons'; // ── Apps with entity capabilities ─────────────────────────── @@ -1169,6 +1171,45 @@ registerApp({ }, }); +registerApp({ + id: 'wishes', + name: 'Wünsche', + color: '#F59E0B', + icon: ShootingStar, + views: { + list: { load: () => import('$lib/modules/wishes/ListView.svelte') }, + detail: { load: () => import('$lib/modules/wishes/views/DetailView.svelte') }, + }, + contextMenuActions: [ + { + id: 'new-wish', + label: 'Neuer Wunsch', + icon: Plus, + action: () => + window.dispatchEvent( + new CustomEvent('mana:quick-action', { detail: { app: 'wishes', action: 'new' } }) + ), + }, + ], + collection: 'wishesItems', + paramKey: 'wishId', + dragType: 'wish', + getDisplayData: (item) => ({ + title: (item.title as string) || 'Wunsch', + subtitle: item.targetPrice + ? `${(item.targetPrice as number).toLocaleString('de-DE')} €` + : undefined, + }), + createItem: async (data) => { + const { wishesStore } = await import('$lib/modules/wishes/stores/wishes.svelte'); + const wish = await wishesStore.create({ + title: data.title as string, + description: data.description as string | undefined, + }); + return wish.id; + }, +}); + registerApp({ id: 'help', name: 'Hilfe', @@ -1179,6 +1220,16 @@ registerApp({ }, }); +registerApp({ + id: 'wetter', + name: 'Wetter', + color: '#38bdf8', + icon: CloudSun, + views: { + list: { load: () => import('$lib/modules/wetter/ListView.svelte') }, + }, +}); + registerApp({ id: 'feedback', name: 'Feedback', diff --git a/apps/mana/apps/web/src/lib/app-registry/categories.ts b/apps/mana/apps/web/src/lib/app-registry/categories.ts index f1db37043..1ea0ee07f 100644 --- a/apps/mana/apps/web/src/lib/app-registry/categories.ts +++ b/apps/mana/apps/web/src/lib/app-registry/categories.ts @@ -76,6 +76,7 @@ export const APP_CATEGORY_MAP: Record = { places: 'life', citycorners: 'life', news: 'life', + wetter: 'life', inventory: 'life', storage: 'life', who: 'life', diff --git a/apps/mana/apps/web/src/lib/modules/wetter/ListView.svelte b/apps/mana/apps/web/src/lib/modules/wetter/ListView.svelte new file mode 100644 index 000000000..e24ae893f --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wetter/ListView.svelte @@ -0,0 +1,289 @@ + + + +{#if weatherStore.loading && !weatherStore.weatherData} +
+ 🌤 + Laden... +
+{:else if weatherStore.weatherData} + {@const data = weatherStore.weatherData} + {@const c = data.current} + + +
+
+ {getWeatherIcon(c.weatherCode, c.isDay)} + {Math.round(c.temperature)}° +
+ {getWeatherLabel(c.weatherCode)} + {data.location.name} +
+
+
+ Gefuehlt {Math.round(c.feelsLike)}° + 💨 {Math.round(c.windSpeed)} km/h {windDirectionLabel(c.windDirection)} + 💧 {c.humidity}% +
+
+ + + {#if data.alerts.length > 0} +
+ {#each data.alerts.slice(0, 2) as alert (alert.id)} +
+ ⚠ {alert.event} +
+ {/each} +
+ {/if} + + + {#if data.hourly.length > 0} +
+ {#each data.hourly.slice(0, 8) as hour (hour.time)} + {@const time = new Date(hour.time).toLocaleTimeString('de-DE', { hour: '2-digit' })} +
+ {time} + {getWeatherIcon(hour.weatherCode, hour.isDay)} + {Math.round(hour.temperature)}° +
+ {/each} +
+ {/if} + + + {#if data.daily.length > 0} +
+ {#each data.daily.slice(0, 5) as day, idx (day.date)} + {@const label = + idx === 0 + ? 'Heute' + : new Date(day.date).toLocaleDateString('de-DE', { weekday: 'short' })} +
+ {label} + {getWeatherIcon(day.weatherCode)} + {Math.round(day.temperatureMin)}° / {Math.round(day.temperatureMax)}° +
+ {/each} +
+ {/if} + + + {#if weatherStore.nowcast} +
+ {weatherStore.nowcast.summary} +
+ {/if} +{:else if weatherStore.error} +
{weatherStore.error}
+{/if} + +