diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts index 634293e66..13a0a5769 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts +++ b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts @@ -34,6 +34,7 @@ import PeriodWidget from '$lib/modules/core/widgets/PeriodWidget.svelte'; import NewsUnreadWidget from '$lib/modules/news/widgets/NewsUnreadWidget.svelte'; import BodyStatsWidget from '$lib/modules/body/widgets/BodyStatsWidget.svelte'; import InvoicesOpenWidget from '$lib/modules/invoices/widgets/InvoicesOpenWidget.svelte'; +import BroadcastsWidget from '$lib/modules/broadcast/widgets/BroadcastsWidget.svelte'; import DayTimelineWidget from './widgets/DayTimelineWidget.svelte'; import ActivityFeedWidget from './widgets/ActivityFeedWidget.svelte'; @@ -64,4 +65,5 @@ export const widgetComponents: Record = { 'news-unread': NewsUnreadWidget, 'body-stats': BodyStatsWidget, 'invoices-open': InvoicesOpenWidget, + broadcasts: BroadcastsWidget, }; diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index d54c70412..b288cc124 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -43,6 +43,7 @@ import { wetterTools } from '$lib/modules/wetter/tools'; import { quizTools } from '$lib/modules/quiz/tools'; import { invoicesTools } from '$lib/modules/invoices/tools'; import { libraryTools } from '$lib/modules/library/tools'; +import { broadcastTools } from '$lib/modules/broadcast/tools'; let initialized = false; @@ -87,5 +88,6 @@ export function initTools(): void { registerTools(quizTools); registerTools(invoicesTools); registerTools(libraryTools); + registerTools(broadcastTools); initialized = true; } diff --git a/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte b/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte index d8989260d..621eecb00 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte @@ -1,19 +1,30 @@ + +
+
+

+ + Broadcasts +

+ Alle → +
+ + {#if loading} +
+ {#each Array(2) as _} +
+ {/each} +
+ {:else if campaigns.length === 0} +
+

Noch keine Kampagnen.

+ + Erste Kampagne + +
+ {:else} +
+
+
Versendet {currentYear}
+
{stats.sentThisYear}
+
+
+
Ø Öffnung
+
+ {formatRate(stats.avgOpenRate)} +
+
+
+ + {#if nextScheduled} + +
+
+
Als nächstes
+
{nextScheduled.name}
+
+
+ {new Date(nextScheduled.scheduledAt ?? '').toLocaleDateString()} +
+
+
+ {/if} + + {#if lastSent} + +
+
+
Zuletzt versendet
+
{lastSent.name}
+
+ {#if lastOpenRate !== null} +
+ {formatRate(lastOpenRate)} 👀 +
+ {/if} +
+
+ {/if} + + {#if !lastSent && !nextScheduled} +

+ {stats.totalByStatus.draft} Entwurf{stats.totalByStatus.draft === 1 ? '' : 'e'} in Arbeit. +

+ {/if} + {/if} +
diff --git a/apps/mana/apps/web/src/lib/types/dashboard.ts b/apps/mana/apps/web/src/lib/types/dashboard.ts index 779bd324e..ea96ed334 100644 --- a/apps/mana/apps/web/src/lib/types/dashboard.ts +++ b/apps/mana/apps/web/src/lib/types/dashboard.ts @@ -33,7 +33,8 @@ export type WidgetType = | 'period' // Period: current phase + days until next period | 'news-unread' // News: latest unread curated articles | 'body-stats' // Body: latest weight + active workout summary - | 'invoices-open'; // Invoices: open/overdue totals + oldest overdue + | 'invoices-open' // Invoices: open/overdue totals + oldest overdue + | 'broadcasts'; // Broadcast: YTD counts + last sent + next scheduled /** * Widget size - maps to CSS Grid columns @@ -371,6 +372,14 @@ export const WIDGET_REGISTRY: WidgetMeta[] = [ defaultSize: 'medium', allowMultiple: false, }, + { + type: 'broadcasts', + nameKey: 'dashboard.widgets.broadcasts.title', + descriptionKey: 'dashboard.widgets.broadcasts.description', + icon: '📣', + defaultSize: 'medium', + allowMultiple: false, + }, ]; /** diff --git a/packages/shared-ai/src/tools/schemas.ts b/packages/shared-ai/src/tools/schemas.ts index 69d2925d6..c70bd24cc 100644 --- a/packages/shared-ai/src/tools/schemas.ts +++ b/packages/shared-ai/src/tools/schemas.ts @@ -1323,6 +1323,79 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [ }, ], }, + + // ── Broadcast (Newsletter) ─────────────────────────────── + { + name: 'create_campaign_draft', + module: 'broadcast', + description: + 'Erstellt einen Newsletter-/Kampagnen-Entwurf mit Name, Betreff, optionalem Preheader und fertigem HTML-Body. Empfaengerliste bleibt leer — der Nutzer waehlt sie in der UI. Gibt die ID zurueck.', + defaultPolicy: 'propose', + parameters: [ + { + name: 'name', + type: 'string', + description: 'Interner Arbeitstitel der Kampagne', + required: true, + }, + { + name: 'subject', + type: 'string', + description: 'E-Mail-Betreff (was im Posteingang steht)', + required: true, + }, + { + name: 'preheader', + type: 'string', + description: 'Vorschau-Text neben dem Betreff in Gmail', + required: false, + }, + { + name: 'htmlContent', + type: 'string', + description: + 'Body als HTML. Erlaubte Tags: p, h1, h2, h3, ul, ol, li, a, strong, em, br. Links verwenden href="https://…".', + required: true, + }, + ], + }, + { + name: 'list_campaigns', + module: 'broadcast', + description: + 'Listet Kampagnen (id, name, subject, status, Empfaengerzahl, sentAt) — optional nach Status gefiltert.', + defaultPolicy: 'auto', + parameters: [ + { + name: 'status', + type: 'string', + description: 'Nur diesen Status zeigen', + required: false, + enum: ['draft', 'scheduled', 'sending', 'sent', 'cancelled'], + }, + { + name: 'limit', + type: 'number', + description: 'Maximale Anzahl (Standard 20)', + required: false, + }, + ], + }, + { + name: 'get_campaign_stats', + module: 'broadcast', + description: + 'Gibt Kennzahlen zu einer Kampagne zurueck: Oeffnungsrate, Klickrate, Bounce-Rate, Abmelderate (jeweils 0..1).', + defaultPolicy: 'auto', + parameters: [ + { + name: 'campaignId', + type: 'string', + description: 'ID der Kampagne (aus list_campaigns)', + required: true, + }, + ], + }, ]; // ═══════════════════════════════════════════════════════════════