591 lines
22 KiB
TypeScript
591 lines
22 KiB
TypeScript
'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; |