243 lines
8.4 KiB
TypeScript
243 lines
8.4 KiB
TypeScript
/**
|
|
* CommCoach Settings View
|
|
*
|
|
* User profile settings: voice preferences, reminders, email notifications.
|
|
*/
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useApiRequest } from '../../../hooks/useApi';
|
|
import { useInstanceId } from '../../../hooks/useCurrentInstance';
|
|
import {
|
|
getProfileApi, updateProfileApi,
|
|
getVoiceLanguagesApi, getVoiceVoicesApi, testVoiceApi,
|
|
type CoachingUserProfile,
|
|
} from '../../../api/commcoachApi';
|
|
import styles from './CommcoachSettingsView.module.css';
|
|
|
|
export const CommcoachSettingsView: React.FC = () => {
|
|
const { request } = useApiRequest();
|
|
const instanceId = useInstanceId();
|
|
|
|
const [profile, setProfile] = useState<CoachingUserProfile | null>(null);
|
|
const [languages, setLanguages] = useState<any[]>([]);
|
|
const [voices, setVoices] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [testing, setTesting] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState<string | null>(null);
|
|
|
|
const [language, setLanguage] = useState('de-DE');
|
|
const [voiceId, setVoiceId] = useState('');
|
|
const [reminderEnabled, setReminderEnabled] = useState(false);
|
|
const [reminderTime, setReminderTime] = useState('09:00');
|
|
const [emailEnabled, setEmailEnabled] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (!instanceId) return;
|
|
const loadData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [profileData, languagesData] = await Promise.all([
|
|
getProfileApi(request, instanceId),
|
|
getVoiceLanguagesApi(request, instanceId),
|
|
]);
|
|
setProfile(profileData);
|
|
setLanguages(languagesData || []);
|
|
|
|
if (profileData) {
|
|
setLanguage(profileData.preferredLanguage || 'de-DE');
|
|
setVoiceId(profileData.preferredVoice || '');
|
|
setReminderEnabled(profileData.dailyReminderEnabled || false);
|
|
setReminderTime(profileData.dailyReminderTime || '09:00');
|
|
setEmailEnabled(profileData.emailSummaryEnabled !== false);
|
|
}
|
|
|
|
const voicesData = await getVoiceVoicesApi(request, instanceId, profileData?.preferredLanguage || 'de-DE');
|
|
setVoices(voicesData || []);
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Laden');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
loadData();
|
|
}, [request, instanceId]);
|
|
|
|
const handleLanguageChange = useCallback(async (newLang: string) => {
|
|
setLanguage(newLang);
|
|
if (!instanceId) return;
|
|
try {
|
|
const voicesData = await getVoiceVoicesApi(request, instanceId, newLang);
|
|
setVoices(voicesData || []);
|
|
setVoiceId('');
|
|
} catch { /* ignore */ }
|
|
}, [request, instanceId]);
|
|
|
|
const handleSave = useCallback(async () => {
|
|
if (!instanceId) return;
|
|
setSaving(true);
|
|
setError(null);
|
|
setSuccess(null);
|
|
try {
|
|
const updated = await updateProfileApi(request, instanceId, {
|
|
preferredLanguage: language,
|
|
preferredVoice: voiceId || null,
|
|
dailyReminderEnabled: reminderEnabled,
|
|
dailyReminderTime: reminderTime,
|
|
emailSummaryEnabled: emailEnabled,
|
|
});
|
|
setProfile(updated);
|
|
setSuccess('Einstellungen gespeichert');
|
|
setTimeout(() => setSuccess(null), 3000);
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Speichern');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}, [request, instanceId, language, voiceId, reminderEnabled, reminderTime, emailEnabled]);
|
|
|
|
const handleTestVoice = useCallback(async () => {
|
|
if (!instanceId) return;
|
|
setTesting(true);
|
|
try {
|
|
const result = await testVoiceApi(request, instanceId, {
|
|
language,
|
|
voiceId: voiceId || undefined,
|
|
});
|
|
if (result.success && result.audio) {
|
|
const audioData = `data:audio/mp3;base64,${result.audio}`;
|
|
const audio = new Audio(audioData);
|
|
audio.play();
|
|
}
|
|
} catch (err: any) {
|
|
setError('Sprachtest fehlgeschlagen');
|
|
} finally {
|
|
setTesting(false);
|
|
}
|
|
}, [request, instanceId, language, voiceId]);
|
|
|
|
if (loading) {
|
|
return <div className={styles.loading}>Einstellungen werden geladen...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className={styles.settings}>
|
|
<h2 className={styles.heading}>Coaching-Einstellungen</h2>
|
|
|
|
{error && <div className={styles.error}>{error}</div>}
|
|
{success && <div className={styles.success}>{success}</div>}
|
|
|
|
{/* Voice Settings */}
|
|
<div className={styles.section}>
|
|
<h3 className={styles.sectionTitle}>Sprache und Stimme</h3>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>Sprache</label>
|
|
<select className={styles.select} value={language} onChange={e => handleLanguageChange(e.target.value)}>
|
|
{languages.length > 0 ? (
|
|
languages.map((lang: any) => (
|
|
<option key={lang.code || lang} value={lang.code || lang}>
|
|
{lang.name || lang.code || lang}
|
|
</option>
|
|
))
|
|
) : (
|
|
<>
|
|
<option value="de-DE">Deutsch</option>
|
|
<option value="en-US">English (US)</option>
|
|
<option value="fr-FR">Francais</option>
|
|
</>
|
|
)}
|
|
</select>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>Stimme</label>
|
|
<div className={styles.voiceRow}>
|
|
<select className={styles.select} value={voiceId} onChange={e => setVoiceId(e.target.value)}>
|
|
<option value="">Standard</option>
|
|
{voices.map((v: any) => (
|
|
<option key={v.name || v} value={v.name || v}>
|
|
{v.displayName || v.name || v}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button className={styles.testBtn} onClick={handleTestVoice} disabled={testing}>
|
|
{testing ? 'Teste...' : 'Testen'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Reminder Settings */}
|
|
<div className={styles.section}>
|
|
<h3 className={styles.sectionTitle}>Erinnerungen</h3>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.checkboxLabel}>
|
|
<input
|
|
type="checkbox"
|
|
checked={reminderEnabled}
|
|
onChange={e => setReminderEnabled(e.target.checked)}
|
|
/>
|
|
Taegliche Coaching-Erinnerung per E-Mail
|
|
</label>
|
|
</div>
|
|
|
|
{reminderEnabled && (
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>Uhrzeit</label>
|
|
<input
|
|
type="time"
|
|
className={styles.input}
|
|
value={reminderTime}
|
|
onChange={e => setReminderTime(e.target.value)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.checkboxLabel}>
|
|
<input
|
|
type="checkbox"
|
|
checked={emailEnabled}
|
|
onChange={e => setEmailEnabled(e.target.checked)}
|
|
/>
|
|
Session-Zusammenfassung per E-Mail senden
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
{profile && (
|
|
<div className={styles.section}>
|
|
<h3 className={styles.sectionTitle}>Statistik</h3>
|
|
<div className={styles.statsGrid}>
|
|
<div className={styles.statItem}>
|
|
<span className={styles.statValue}>{profile.totalSessions}</span>
|
|
<span className={styles.statLabel}>Sessions gesamt</span>
|
|
</div>
|
|
<div className={styles.statItem}>
|
|
<span className={styles.statValue}>{profile.totalMinutes}</span>
|
|
<span className={styles.statLabel}>Minuten gesamt</span>
|
|
</div>
|
|
<div className={styles.statItem}>
|
|
<span className={styles.statValue}>{profile.streakDays}</span>
|
|
<span className={styles.statLabel}>Aktueller Streak</span>
|
|
</div>
|
|
<div className={styles.statItem}>
|
|
<span className={styles.statValue}>{profile.longestStreak}</span>
|
|
<span className={styles.statLabel}>Laengster Streak</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<button className={styles.saveBtn} onClick={handleSave} disabled={saving}>
|
|
{saving ? 'Speichern...' : 'Einstellungen speichern'}
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CommcoachSettingsView;
|