This commit is contained in:
beabigegg
2025-09-01 08:51:39 +08:00
parent f3f2b7d596
commit 45a42f8e64
16 changed files with 5438 additions and 48 deletions

View File

@@ -0,0 +1,591 @@
'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;