feat(notifications): add per-tenant notification preferences
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
} from '@/src/lib/server-config';
|
||||
import { useAuth } from '@/src/providers/auth-provider';
|
||||
|
||||
import { NotificationPreferences } from '@/src/components/NotificationPreferences';
|
||||
|
||||
const PRIMARY = '#69c350';
|
||||
|
||||
function isValidServerUrl(value: string): boolean {
|
||||
@@ -153,6 +155,7 @@ export default function SettingsScreen() {
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{token && <NotificationPreferences key={String(activeTenantId)} token={token} />}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.title}>Server-Instanz</Text>
|
||||
<Text style={styles.hint}>
|
||||
|
||||
43
mobile/src/components/NotificationPreferences.tsx
Normal file
43
mobile/src/components/NotificationPreferences.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Pressable, Switch, Text, View } from 'react-native';
|
||||
import { fetchNotificationPreferences, saveNotificationPreferences, type NotificationPreferenceEvent } from '@/src/lib/api';
|
||||
|
||||
export function NotificationPreferences({ token }: { token: string }) {
|
||||
const [events, setEvents] = useState<NotificationPreferenceEvent[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setLoading(true);
|
||||
fetchNotificationPreferences(token).then(result => { if (active) setEvents(result.events); })
|
||||
.catch(error => { if (active) setMessage(error.message); })
|
||||
.finally(() => { if (active) setLoading(false); });
|
||||
return () => { active = false; };
|
||||
}, [token]);
|
||||
async function save() {
|
||||
setSaving(true);
|
||||
setMessage('');
|
||||
try { await saveNotificationPreferences(token, events); setMessage('Einstellungen gespeichert.'); }
|
||||
catch (error) { setMessage(error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'); }
|
||||
finally { setSaving(false); }
|
||||
}
|
||||
return <View style={{ gap: 12, padding: 16, backgroundColor: '#fff', borderRadius: 12 }}>
|
||||
<Text style={{ fontSize: 18, fontWeight: '600' }}>Meine Benachrichtigungen</Text>
|
||||
<Text>Gilt nur für dein Profil im aktuell ausgewählten Mandanten. E-Mail geht an deine Konto-Adresse, Push an deine registrierten Geräte.</Text>
|
||||
{loading && <Text>Einstellungen werden geladen …</Text>}
|
||||
{!!message && <Text accessibilityRole="alert">{message}</Text>}
|
||||
{!loading && events.map(event => <View key={event.eventKey} style={{ gap: 4 }}>
|
||||
<Text style={{ fontWeight: '600' }}>{event.displayName}</Text>
|
||||
<Text>{event.description}</Text>
|
||||
{(['email', 'push'] as const).filter(channel => channel in event.channels).map(channel => <View key={channel} style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text>{channel === 'email' ? 'E-Mail' : 'Push'}</Text>
|
||||
<Switch accessibilityLabel={`${event.displayName}: ${channel}`} disabled={saving} value={event.channels[channel] === true}
|
||||
onValueChange={enabled => setEvents(rows => rows.map(row => row.eventKey === event.eventKey ? { ...row, channels: { ...row.channels, [channel]: enabled } } : row))} />
|
||||
</View>)}
|
||||
</View>)}
|
||||
{!loading && events.length > 0 && <Pressable accessibilityRole="button" disabled={saving} onPress={save} style={{ padding: 12, backgroundColor: '#69c350', borderRadius: 8 }}>
|
||||
<Text>{saving ? 'Speichert …' : 'Speichern'}</Text>
|
||||
</Pressable>}
|
||||
</View>;
|
||||
}
|
||||
@@ -1301,3 +1301,17 @@ export async function uploadPlantFile(
|
||||
|
||||
return parsed as ProjectFile;
|
||||
}
|
||||
|
||||
export type NotificationPreferenceEvent = {
|
||||
eventKey: string;
|
||||
displayName: string;
|
||||
description: string | null;
|
||||
channels: Partial<Record<'email' | 'push', boolean>>;
|
||||
};
|
||||
export async function fetchNotificationPreferences(token: string) {
|
||||
return apiRequest<{ tenantId: number; events: NotificationPreferenceEvent[] }>('/api/notifications/preferences', { token });
|
||||
}
|
||||
export async function saveNotificationPreferences(token: string, events: NotificationPreferenceEvent[]) {
|
||||
return apiRequest('/api/notifications/preferences', { token, method: 'PUT', body: events.flatMap(event =>
|
||||
Object.entries(event.channels).map(([channel, enabled]) => ({ eventType: event.eventKey, channel, enabled }))) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user