Files
TODO_list_system/frontend/src/components/notifications/EnhancedEmailNotificationSettings.tsx
beabigegg 45a42f8e64 3RD
2025-09-01 08:51:39 +08:00

591 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Box,
Typography,
Switch,
FormControlLabel,
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
Button,
Divider,
Alert,
Card,
CardContent,
Chip,
IconButton,
Checkbox,
FormGroup,
Grid,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material';
import {
Email,
Schedule,
Close,
NotificationImportant,
Settings,
Save,
ExpandMore,
Alarm,
Today,
CalendarMonth,
AccessTime,
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { useTheme } from '@/providers/ThemeProvider';
import { useAuth } from '@/providers/AuthProvider';
import { notificationsApi } from '@/lib/api';
import { toast } from 'react-hot-toast';
interface EnhancedEmailNotificationSettingsProps {
open: boolean;
onClose: () => void;
}
interface NotificationSettings {
// 基本設定
emailEnabled: boolean;
emailAddress: string;
// 到期提醒設定 - 支援多個天數
reminderDays: number[];
// 每日摘要
dailyDigestEnabled: boolean;
dailyDigestTime: string;
// 週報摘要
weeklyDigestEnabled: boolean;
weeklyDigestTime: string;
weeklyDigestDay: number; // 0=週日, 1=週一...
// 月報摘要
monthlyDigestEnabled: boolean;
monthlyDigestTime: string;
monthlyDigestDay: number; // 每月第幾日
// 其他通知
assignmentNotifications: boolean;
completionNotifications: boolean;
}
const EnhancedEmailNotificationSettings: React.FC<EnhancedEmailNotificationSettingsProps> = ({
open,
onClose,
}) => {
const { actualTheme } = useTheme();
const { user } = useAuth();
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [testEmailLoading, setTestEmailLoading] = useState(false);
const [settings, setSettings] = useState<NotificationSettings>({
emailEnabled: false,
emailAddress: user?.email || '',
reminderDays: [1, 3], // 預設前1天、前3天
dailyDigestEnabled: false,
dailyDigestTime: '09:00',
weeklyDigestEnabled: true,
weeklyDigestTime: '09:00',
weeklyDigestDay: 1, // 週一
monthlyDigestEnabled: false,
monthlyDigestTime: '09:00',
monthlyDigestDay: 1, // 每月1日
assignmentNotifications: true,
completionNotifications: false,
});
// 可選的提醒天數選項
const reminderDayOptions = [1, 2, 3, 5, 7, 14];
// 週幾選項
const weekDayOptions = [
{ value: 0, label: '週日' },
{ value: 1, label: '週一' },
{ value: 2, label: '週二' },
{ value: 3, label: '週三' },
{ value: 4, label: '週四' },
{ value: 5, label: '週五' },
{ value: 6, label: '週六' },
];
// 時間選項
const timeOptions = Array.from({ length: 24 }, (_, i) => {
const hour = i.toString().padStart(2, '0');
return `${hour}:00`;
});
// 載入用戶的通知設定
useEffect(() => {
if (open && user) {
loadSettings();
}
}, [open, user]);
const loadSettings = async () => {
try {
setLoading(true);
const data = await notificationsApi.getSettings();
setSettings(prev => ({
...prev,
emailEnabled: data.email_reminder_enabled || false,
reminderDays: data.reminder_days_before || [1, 3],
dailyDigestEnabled: false, // 暫時沒有每日摘要
weeklyDigestEnabled: data.weekly_summary_enabled || false,
weeklyDigestTime: data.weekly_summary_time || '09:00',
weeklyDigestDay: data.weekly_summary_day || 1,
monthlyDigestEnabled: data.monthly_summary_enabled || false,
monthlyDigestTime: data.monthly_summary_time || '09:00',
monthlyDigestDay: data.monthly_summary_day || 1,
assignmentNotifications: data.notification_enabled || true,
emailAddress: user?.email || data.email || '',
}));
} catch (error) {
console.error('Failed to load notification settings:', error);
} finally {
setLoading(false);
}
};
const handleSave = async () => {
try {
setSaving(true);
const payload = {
email_reminder_enabled: settings.emailEnabled,
notification_enabled: settings.assignmentNotifications,
weekly_summary_enabled: settings.weeklyDigestEnabled,
monthly_summary_enabled: settings.monthlyDigestEnabled,
reminder_days_before: settings.reminderDays,
weekly_summary_time: settings.weeklyDigestTime,
weekly_summary_day: settings.weeklyDigestDay,
monthly_summary_time: settings.monthlyDigestTime,
monthly_summary_day: settings.monthlyDigestDay,
};
await notificationsApi.updateSettings(payload);
toast.success('通知設定已儲存');
onClose();
} catch (error) {
console.error('Failed to save notification settings:', error);
toast.error('儲存通知設定失敗,請檢查網路連線');
} finally {
setSaving(false);
}
};
const handleTestEmail = async () => {
try {
setTestEmailLoading(true);
await notificationsApi.sendTestEmail(settings.emailAddress);
toast.success(`測試郵件已發送至 ${settings.emailAddress}!請檢查您的信箱`);
} catch (error) {
console.error('Failed to send test email:', error);
toast.error('發送測試郵件失敗,請檢查網路連線');
} finally {
setTestEmailLoading(false);
}
};
const handleReminderDayToggle = (day: number) => {
setSettings(prev => ({
...prev,
reminderDays: prev.reminderDays.includes(day)
? prev.reminderDays.filter(d => d !== day)
: [...prev.reminderDays, day].sort((a, b) => a - b)
}));
};
if (loading) {
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogContent>
<Box display="flex" justifyContent="center" py={4}>
<Typography>...</Typography>
</Box>
</DialogContent>
</Dialog>
);
}
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="md"
fullWidth
sx={{
'& .MuiDialog-paper': {
backgroundColor: actualTheme === 'dark' ? '#1f2937' : '#ffffff',
borderRadius: 3,
minHeight: 700,
}
}}
>
<DialogTitle sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
pb: 1
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Email sx={{ color: 'primary.main' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
</Typography>
</Box>
<IconButton onClick={onClose} size="small">
<Close />
</IconButton>
</DialogTitle>
<DialogContent sx={{ px: 3 }}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* 總開關 */}
<Card sx={{ mb: 3, backgroundColor: actualTheme === 'dark' ? '#374151' : '#f8fafc' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<Typography variant="h6" sx={{ mb: 0.5 }}>
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
<Switch
checked={settings.emailEnabled}
onChange={(e) => setSettings(prev => ({ ...prev, emailEnabled: e.target.checked }))}
size="medium"
/>
</Box>
</CardContent>
</Card>
{settings.emailEnabled && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
{/* 基本設定 */}
<Accordion defaultExpanded sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Settings />
<Typography variant="h6"></Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<TextField
fullWidth
label="郵件地址"
value={settings.emailAddress}
onChange={(e) => setSettings(prev => ({ ...prev, emailAddress: e.target.value }))}
helperText="通知將發送至此郵件地址"
sx={{ mb: 2 }}
disabled
/>
<Button
variant="outlined"
size="small"
onClick={handleTestEmail}
disabled={!settings.emailAddress || testEmailLoading}
sx={{ mb: 2 }}
>
{testEmailLoading ? '發送中...' : '發送測試郵件'}
</Button>
</AccordionDetails>
</Accordion>
{/* 到期提醒設定 */}
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Alarm />
<Typography variant="h6"></Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
</Typography>
<FormGroup>
<Grid container spacing={1}>
{reminderDayOptions.map(day => (
<Grid item xs={6} sm={4} key={day}>
<FormControlLabel
control={
<Checkbox
checked={settings.reminderDays.includes(day)}
onChange={() => handleReminderDayToggle(day)}
size="small"
/>
}
label={`${day}`}
/>
</Grid>
))}
</Grid>
</FormGroup>
{settings.reminderDays.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="caption" color="text.secondary">
</Typography>
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 1 }}>
{settings.reminderDays.sort((a, b) => a - b).map(day => (
<Chip
key={day}
size="small"
label={`${day}`}
color="primary"
variant="outlined"
/>
))}
</Box>
</Box>
)}
</AccordionDetails>
</Accordion>
{/* 摘要郵件設定 */}
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Schedule />
<Typography variant="h6"></Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
{/* 週報設定 */}
<Grid item xs={12} md={6}>
<Card sx={{ p: 2, border: `1px solid ${actualTheme === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'}` }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CalendarMonth sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
</Typography>
</Box>
<FormControlLabel
control={
<Switch
checked={settings.weeklyDigestEnabled}
onChange={(e) => setSettings(prev => ({ ...prev, weeklyDigestEnabled: e.target.checked }))}
/>
}
label="啟用週報"
sx={{ mb: 2 }}
/>
{settings.weeklyDigestEnabled && (
<Box>
<FormControl size="small" fullWidth sx={{ mb: 1 }}>
<InputLabel></InputLabel>
<Select
value={settings.weeklyDigestTime}
label="發送時間"
onChange={(e) => setSettings(prev => ({ ...prev, weeklyDigestTime: e.target.value }))}
>
{timeOptions.map(time => (
<MenuItem key={time} value={time}>{time}</MenuItem>
))}
</Select>
</FormControl>
<FormControl size="small" fullWidth>
<InputLabel></InputLabel>
<Select
value={settings.weeklyDigestDay}
label="發送日期"
onChange={(e) => setSettings(prev => ({ ...prev, weeklyDigestDay: e.target.value as number }))}
>
{weekDayOptions.map(option => (
<MenuItem key={option.value} value={option.value}>{option.label}</MenuItem>
))}
</Select>
</FormControl>
</Box>
)}
</Card>
</Grid>
{/* 月報設定 */}
<Grid item xs={12} md={6}>
<Card sx={{ p: 2, border: `1px solid ${actualTheme === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'}` }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Today sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
</Typography>
</Box>
<FormControlLabel
control={
<Switch
checked={settings.monthlyDigestEnabled}
onChange={(e) => setSettings(prev => ({ ...prev, monthlyDigestEnabled: e.target.checked }))}
/>
}
label="啟用月報"
sx={{ mb: 2 }}
/>
{settings.monthlyDigestEnabled && (
<Box>
<FormControl size="small" fullWidth sx={{ mb: 1 }}>
<InputLabel></InputLabel>
<Select
value={settings.monthlyDigestTime}
label="發送時間"
onChange={(e) => setSettings(prev => ({ ...prev, monthlyDigestTime: e.target.value }))}
>
{timeOptions.map(time => (
<MenuItem key={time} value={time}>{time}</MenuItem>
))}
</Select>
</FormControl>
<TextField
size="small"
type="number"
fullWidth
label="每月第幾日"
value={settings.monthlyDigestDay}
onChange={(e) => setSettings(prev => ({ ...prev, monthlyDigestDay: Math.max(1, Math.min(28, parseInt(e.target.value) || 1)) }))}
inputProps={{ min: 1, max: 28 }}
helperText="1-28日"
/>
</Box>
)}
</Card>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
{/* 其他通知設定 */}
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<NotificationImportant />
<Typography variant="h6"></Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<FormControlLabel
control={
<Switch
checked={settings.assignmentNotifications}
onChange={(e) => setSettings(prev => ({ ...prev, assignmentNotifications: e.target.checked }))}
/>
}
label={
<Box>
<Typography variant="subtitle2"></Typography>
<Typography variant="caption" color="text.secondary" component="div">
</Typography>
</Box>
}
sx={{ mb: 2, alignItems: 'flex-start', ml: 0 }}
/>
<FormControlLabel
control={
<Switch
checked={settings.completionNotifications}
onChange={(e) => setSettings(prev => ({ ...prev, completionNotifications: e.target.checked }))}
/>
}
label={
<Box>
<Typography variant="subtitle2"></Typography>
<Typography variant="caption" color="text.secondary" component="div">
</Typography>
</Box>
}
sx={{ alignItems: 'flex-start', ml: 0 }}
/>
</AccordionDetails>
</Accordion>
{/* 設定預覽 */}
<Alert
severity="info"
sx={{
mb: 2,
backgroundColor: actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.1)' : 'rgba(59, 130, 246, 0.05)',
color: actualTheme === 'dark' ? '#93c5fd' : '#1d4ed8',
}}
>
<Typography variant="body2" sx={{ mb: 1 }}>
<strong></strong>
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{settings.reminderDays.length > 0 && (
<Chip size="small" label={`到期提醒:前${settings.reminderDays.join('、')}`} />
)}
{settings.weeklyDigestEnabled && (
<Chip size="small" label={`週報:${weekDayOptions.find(d => d.value === settings.weeklyDigestDay)?.label} ${settings.weeklyDigestTime}`} />
)}
{settings.monthlyDigestEnabled && (
<Chip size="small" label={`月報:每月${settings.monthlyDigestDay}${settings.monthlyDigestTime}`} />
)}
{settings.assignmentNotifications && (
<Chip size="small" label="指派通知" />
)}
{settings.completionNotifications && (
<Chip size="small" label="完成通知" />
)}
</Box>
</Alert>
</motion.div>
)}
</motion.div>
</DialogContent>
<DialogActions sx={{ px: 3, py: 2 }}>
<Button onClick={onClose} color="inherit">
</Button>
<Button
onClick={handleSave}
variant="contained"
startIcon={<Save />}
disabled={saving}
sx={{ minWidth: 100 }}
>
{saving ? '儲存中...' : '儲存設定'}
</Button>
</DialogActions>
</Dialog>
);
};
export default EnhancedEmailNotificationSettings;