feat: add daily digest email notification module
Build and Push Multi-Platform Images / build-and-push (push) Successful in 50s

- New DailyDigestModule with scheduled summary email for open dashboard items
- Extract StatsService from StatsController for reuse in digest
- Add DailyDigestEnabled, UserEmail, UserPreferredUsername to UserSettings entity
- Sync email/username from OIDC token on each get/update call
- Add dailyDigestEnabled to UserSettingsDto and update API
- Notifications tab in UserSettingsPage with enable toggle and "Jetzt senden" button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 15:57:10 +02:00
parent 029d5b351f
commit 52438ee11f
12 changed files with 366 additions and 86 deletions
@@ -11,6 +11,7 @@ export interface UserSettingsData {
mailSignatureHtml: string | null;
defaultLabelTemplateId: number | null;
emailRecipientHistory: string[] | null;
dailyDigestEnabled: boolean;
}
export interface SenderOption {
@@ -31,4 +32,7 @@ export const userSettingsApi = {
getSenders: () =>
api.get<SenderOption[]>('/api/user-settings/senders').then((r) => r.data),
sendDigestNow: () =>
api.post<{ ok: boolean; error?: string }>('/api/daily-digest/send-now').then((r) => r.data),
};
@@ -197,6 +197,71 @@ function MailSettingsTab() {
);
}
function NotificationsTab() {
const [enabled, setEnabled] = useState(false);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [sending, setSending] = useState(false);
useEffect(() => {
userSettingsApi.get()
.then((data) => setEnabled(data.dailyDigestEnabled ?? false))
.catch(() => message.error('Einstellungen konnten nicht geladen werden'))
.finally(() => setLoading(false));
}, []);
const handleSave = async () => {
setSaving(true);
try {
await userSettingsApi.update({ dailyDigestEnabled: enabled });
message.success('Einstellungen gespeichert');
} catch {
message.error('Speichern fehlgeschlagen');
} finally {
setSaving(false);
}
};
const handleSendNow = async () => {
setSending(true);
try {
const result = await userSettingsApi.sendDigestNow();
if (result.ok) {
message.success('Tagesübersicht wurde gesendet');
} else {
message.error(result.error ?? 'Senden fehlgeschlagen');
}
} catch {
message.error('Senden fehlgeschlagen');
} finally {
setSending(false);
}
};
if (loading) return null;
return (
<Form layout="vertical" style={{ maxWidth: 600 }}>
<Form.Item
label="Tägliche E-Mail-Zusammenfassung"
extra="Sie erhalten jeden Morgen eine E-Mail mit der Übersicht aller offenen Vorgänge aus dem Dashboard."
>
<Switch checked={enabled} onChange={setEnabled} />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" loading={saving} onClick={handleSave}>
Speichern
</Button>
<Button loading={sending} onClick={handleSendNow}>
Jetzt senden
</Button>
</Space>
</Form.Item>
</Form>
);
}
export default function UserSettingsPage() {
return (
<div>
@@ -214,6 +279,11 @@ export default function UserSettingsPage() {
label: 'Etikettendruck',
children: <LabelSettingsTab />,
},
{
key: 'notifications',
label: 'Benachrichtigungen',
children: <NotificationsTab />,
},
]}
/>
</Card>