3RD
This commit is contained in:
@@ -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;
|
Reference in New Issue
Block a user