- 缩短 SW 宽限期从10分钟改为3分钟 - 新增 PopoverTimePicker 弹出式时间选择器 - 支持数字输入和30分钟间隔选择 - 替换原有的 WheelTimePicker Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
725 lines
24 KiB
TypeScript
725 lines
24 KiB
TypeScript
import { useEffect, useState, useRef } from 'react';
|
||
import {
|
||
Container,
|
||
Title,
|
||
Button,
|
||
Group,
|
||
Text,
|
||
Modal,
|
||
TextInput,
|
||
Textarea,
|
||
Switch,
|
||
Select,
|
||
Stack,
|
||
Box,
|
||
ActionIcon,
|
||
} from '@mantine/core';
|
||
import { DatePickerInput } from '@mantine/dates';
|
||
import { IconCalendar, IconClock, IconSettings, IconLogout, IconX } from '@tabler/icons-react';
|
||
import { useDisclosure } from '@mantine/hooks';
|
||
import { FixedCalendar } from '../components/common/FixedCalendar';
|
||
import { PopoverTimePicker } from '../components/common/PopoverTimePicker';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { useAppStore } from '../stores';
|
||
import { AnniversaryList } from '../components/anniversary/AnniversaryList';
|
||
import { ReminderList } from '../components/reminder/ReminderList';
|
||
import { NoteEditor } from '../components/note/NoteEditor';
|
||
import { FloatingAIChat } from '../components/ai/FloatingAIChat';
|
||
import appIcon from '../assets/icon.png';
|
||
import type { Event, EventType, RepeatType, PriorityType } from '../types';
|
||
import { calculateNextReminderDate, getReminderOptions, getDefaultReminderValue, calculateReminderTimes, getReminderValueFromTimes, formatReminderTimeDisplay } from '../utils/repeatCalculator';
|
||
|
||
export function HomePage() {
|
||
const navigate = useNavigate();
|
||
const user = useAppStore((state) => state.user);
|
||
const logout = useAppStore((state) => state.logout);
|
||
const checkAuth = useAppStore((state) => state.checkAuth);
|
||
const events = useAppStore((state) => state.events);
|
||
const fetchEvents = useAppStore((state) => state.fetchEvents);
|
||
const createEvent = useAppStore((state) => state.createEvent);
|
||
const updateEventById = useAppStore((state) => state.updateEventById);
|
||
const deleteEventById = useAppStore((state) => state.deleteEventById);
|
||
|
||
const [opened, { open, close }] = useDisclosure(false);
|
||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||
const [isEdit, setIsEdit] = useState(false);
|
||
|
||
// Form state
|
||
const [formType, setFormType] = useState<EventType>('anniversary');
|
||
const [formTitle, setFormTitle] = useState('');
|
||
const [formContent, setFormContent] = useState('');
|
||
const [formDate, setFormDate] = useState<Date | null>(null);
|
||
const [formTime, setFormTime] = useState('');
|
||
const [formIsLunar, setFormIsLunar] = useState(false);
|
||
const [formRepeatType, setFormRepeatType] = useState<RepeatType>('none');
|
||
const [formIsHoliday, setFormIsHoliday] = useState(false);
|
||
const [formPriority, setFormPriority] = useState<PriorityType>('none');
|
||
const [formReminderValue, setFormReminderValue] = useState('0'); // 提醒选项值
|
||
|
||
// Initialize auth and data on mount
|
||
useEffect(() => {
|
||
checkAuth();
|
||
fetchEvents().catch(console.error);
|
||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||
|
||
const handleLogout = async () => {
|
||
await logout();
|
||
navigate('/landing');
|
||
};
|
||
|
||
const handleEventClick = (event: Event) => {
|
||
setSelectedEvent(event);
|
||
setIsEdit(true);
|
||
setFormType(event.type);
|
||
setFormTitle(event.title);
|
||
setFormContent(event.content || '');
|
||
setFormDate(new Date(event.date));
|
||
setFormIsLunar(event.is_lunar);
|
||
setFormRepeatType(event.repeat_type);
|
||
setFormIsHoliday(event.is_holiday || false);
|
||
setFormPriority(event.priority || 'none');
|
||
// 提取时间(如果日期中包含时间信息)
|
||
const eventDate = new Date(event.date);
|
||
const hours = eventDate.getHours();
|
||
const minutes = eventDate.getMinutes();
|
||
const hasTime = hours !== 0 || minutes !== 0;
|
||
setFormTime(hasTime ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}` : '');
|
||
// 从 reminder_times 反推用户选择的提醒选项值
|
||
setFormReminderValue(getReminderValueFromTimes(event.reminder_times, event.date, hasTime));
|
||
open();
|
||
};
|
||
|
||
const handleAddClick = (type: EventType) => {
|
||
setSelectedEvent(null);
|
||
setIsEdit(false);
|
||
setFormType(type);
|
||
setFormTitle('');
|
||
setFormContent('');
|
||
setFormDate(null);
|
||
setFormTime('');
|
||
setFormIsLunar(false);
|
||
setFormRepeatType('none');
|
||
setFormIsHoliday(type === 'anniversary');
|
||
setFormPriority('none');
|
||
setFormReminderValue('0');
|
||
open();
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (!formTitle.trim() || !formDate) return;
|
||
|
||
// 确保 date 是 Date 对象
|
||
const dateObj = formDate instanceof Date ? formDate : new Date(formDate as unknown as string);
|
||
|
||
let dateStr: string;
|
||
const year = dateObj.getFullYear();
|
||
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
||
const day = String(dateObj.getDate()).padStart(2, '0');
|
||
|
||
if (formTime) {
|
||
// 有时间:构建 UTC 时间格式(用户选择的是本地时间,需要转换为 UTC 存储)
|
||
const hours = parseInt(formTime.split(':')[0]);
|
||
const minutes = parseInt(formTime.split(':')[1]);
|
||
// 先用本地时间创建日期对象,然后获取 UTC 时间
|
||
const localDate = new Date(year, dateObj.getMonth(), day, hours, minutes);
|
||
dateStr = localDate.toISOString();
|
||
} else {
|
||
// 无时间:使用 UTC 日期格式
|
||
dateStr = `${year}-${month}-${day}T00:00:00.000Z`;
|
||
}
|
||
|
||
// 构建事件数据,确保不包含 undefined 值
|
||
const eventData: Record<string, any> = {
|
||
type: formType,
|
||
title: formTitle,
|
||
date: dateStr,
|
||
is_lunar: formIsLunar,
|
||
repeat_type: formRepeatType,
|
||
// 计算下一次提醒日期
|
||
next_reminder_date: calculateNextReminderDate(dateStr, formRepeatType, undefined),
|
||
is_holiday: formIsHoliday ?? false,
|
||
priority: formPriority,
|
||
};
|
||
|
||
// 只有当 content 有值时才包含
|
||
if (formContent.trim()) {
|
||
eventData.content = formContent;
|
||
}
|
||
|
||
// 计算提醒时间点(仅对提醒事项)
|
||
if (formType === 'reminder') {
|
||
const hasTime = !!formTime;
|
||
eventData.reminder_times = calculateReminderTimes(dateStr, hasTime, formReminderValue);
|
||
}
|
||
|
||
if (isEdit && selectedEvent) {
|
||
await updateEventById(selectedEvent.id, eventData);
|
||
} else {
|
||
await createEvent(eventData);
|
||
}
|
||
|
||
close();
|
||
resetForm();
|
||
fetchEvents();
|
||
};
|
||
|
||
const handleDeleteFromModal = async () => {
|
||
if (!selectedEvent) return;
|
||
await deleteEventById(selectedEvent.id);
|
||
close();
|
||
resetForm();
|
||
};
|
||
|
||
const handleToggleComplete = async (event: Event) => {
|
||
if (event.type !== 'reminder') return;
|
||
// 使用当前期望的状态(取反)
|
||
const newCompleted = !event.is_completed;
|
||
const result = await updateEventById(event.id, {
|
||
is_completed: newCompleted,
|
||
});
|
||
if (result.error) {
|
||
console.error('更新失败:', result.error);
|
||
}
|
||
// 乐观更新已处理 UI 响应,无需 fetchEvents
|
||
};
|
||
|
||
const handleDelete = async (event: Event) => {
|
||
if (event.type !== 'reminder') return;
|
||
await deleteEventById(event.id);
|
||
fetchEvents();
|
||
};
|
||
|
||
const handlePostpone = async (event: Event) => {
|
||
if (event.type !== 'reminder') return;
|
||
// 将日期顺延到今天,保留原事件的时间
|
||
const today = new Date();
|
||
const originalDate = new Date(event.date);
|
||
|
||
// 提取原时间的小时和分钟
|
||
const hours = originalDate.getHours();
|
||
const minutes = originalDate.getMinutes();
|
||
|
||
// 构建新的本地时间字符串
|
||
const newDateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}T${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`;
|
||
|
||
// 构建更新数据
|
||
const updateData: Record<string, any> = {
|
||
date: newDateStr,
|
||
priority: event.priority || 'none',
|
||
};
|
||
|
||
// 如果是重复提醒,同步更新 next_reminder_date
|
||
if (event.repeat_type && event.repeat_type !== 'none') {
|
||
const nextReminderDate = calculateNextReminderDate(
|
||
newDateStr,
|
||
event.repeat_type as RepeatType,
|
||
event.repeat_interval || undefined
|
||
);
|
||
updateData.next_reminder_date = nextReminderDate;
|
||
}
|
||
|
||
const result = await updateEventById(event.id, updateData);
|
||
if (result.error) {
|
||
console.error('顺延失败:', result.error);
|
||
}
|
||
};
|
||
|
||
const handleDateChange = async (
|
||
event: Event,
|
||
date: string,
|
||
repeatType: string,
|
||
repeatInterval: number | null
|
||
) => {
|
||
if (event.type !== 'reminder') return;
|
||
// 保留原事件的时间信息
|
||
const originalDate = new Date(event.date);
|
||
const hours = originalDate.getHours();
|
||
const minutes = originalDate.getMinutes();
|
||
|
||
// 构建新的本地时间字符串
|
||
const newDateStr = `${date}T${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`;
|
||
|
||
// 构建更新数据
|
||
const updateData: Record<string, any> = {
|
||
date: newDateStr,
|
||
priority: event.priority || 'none',
|
||
};
|
||
|
||
// 如果是重复提醒,同步更新 next_reminder_date
|
||
if (repeatType && repeatType !== 'none') {
|
||
const nextReminderDate = calculateNextReminderDate(
|
||
newDateStr,
|
||
repeatType as RepeatType,
|
||
repeatInterval || undefined
|
||
);
|
||
updateData.next_reminder_date = nextReminderDate;
|
||
}
|
||
|
||
// 合并更新
|
||
const result = await updateEventById(event.id, updateData);
|
||
if (result.error) {
|
||
console.error('日期调整失败:', result.error);
|
||
}
|
||
fetchEvents(); // 刷新列表以更新UI
|
||
};
|
||
|
||
const handlePriorityChange = async (event: Event, priority: PriorityType) => {
|
||
if (event.type !== 'reminder') return;
|
||
// 合并更新 priority 和 date,确保 updated_at 被更新
|
||
const result = await updateEventById(event.id, {
|
||
priority,
|
||
date: event.date,
|
||
});
|
||
if (result.error) {
|
||
console.error('优先级设置失败:', result.error);
|
||
}
|
||
fetchEvents(); // 刷新列表以更新UI
|
||
};
|
||
|
||
const resetForm = () => {
|
||
setFormType('anniversary');
|
||
setFormTitle('');
|
||
setFormContent('');
|
||
setFormDate(null);
|
||
setFormTime('');
|
||
setFormIsLunar(false);
|
||
setFormRepeatType('none');
|
||
setFormIsHoliday(false);
|
||
setFormPriority('none');
|
||
setFormReminderValue('0');
|
||
setSelectedEvent(null);
|
||
setIsEdit(false);
|
||
};
|
||
|
||
const handleAIEventCreated = () => {
|
||
fetchEvents();
|
||
};
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
minHeight: '100vh',
|
||
background: '#faf9f7',
|
||
overflow: 'hidden',
|
||
height: '100vh',
|
||
}}
|
||
>
|
||
<Container size="xl" py="md" h="100vh" style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||
{/* Header */}
|
||
<Group justify="space-between" mb="md" style={{ flexShrink: 0 }}>
|
||
<Group gap="sm">
|
||
<img
|
||
src={appIcon}
|
||
alt="掐日子"
|
||
style={{
|
||
width: 28,
|
||
height: 28,
|
||
borderRadius: '50%',
|
||
}}
|
||
/>
|
||
<Title
|
||
order={2}
|
||
style={{
|
||
fontWeight: 300,
|
||
fontSize: '1.25rem',
|
||
letterSpacing: '0.15em',
|
||
color: '#1a1a1a',
|
||
}}
|
||
>
|
||
掐日子
|
||
</Title>
|
||
</Group>
|
||
<Group>
|
||
{/* 设置入口 */}
|
||
<Button
|
||
variant="subtle"
|
||
color="gray"
|
||
size="xs"
|
||
leftSection={<IconSettings size={12} />}
|
||
onClick={() => navigate('/settings')}
|
||
style={{
|
||
letterSpacing: '0.1em',
|
||
borderRadius: 2,
|
||
}}
|
||
>
|
||
设置
|
||
</Button>
|
||
<Text
|
||
size="xs"
|
||
c="#888"
|
||
style={{ letterSpacing: '0.05em' }}
|
||
>
|
||
{user?.nickname || user?.email}
|
||
</Text>
|
||
<Button
|
||
variant="subtle"
|
||
color="gray"
|
||
size="xs"
|
||
leftSection={<IconLogout size={12} />}
|
||
onClick={handleLogout}
|
||
style={{
|
||
letterSpacing: '0.1em',
|
||
borderRadius: 2,
|
||
}}
|
||
>
|
||
退出
|
||
</Button>
|
||
</Group>
|
||
</Group>
|
||
|
||
{/* Main Content - 3 column horizontal layout */}
|
||
<div style={{ flex: 1, minHeight: 0, display: 'flex', gap: 16, overflow: 'hidden' }}>
|
||
{/* Left column - Anniversary */}
|
||
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||
<AnniversaryList
|
||
events={events}
|
||
onEventClick={handleEventClick}
|
||
onAddClick={() => handleAddClick('anniversary')}
|
||
/>
|
||
</div>
|
||
|
||
{/* Middle column - Reminder */}
|
||
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||
<ReminderList
|
||
events={events}
|
||
onEventClick={handleEventClick}
|
||
onToggleComplete={handleToggleComplete}
|
||
onAddClick={() => handleAddClick('reminder')}
|
||
onDelete={handleDelete}
|
||
onPostpone={handlePostpone}
|
||
onDateChange={handleDateChange}
|
||
onPriorityChange={handlePriorityChange}
|
||
/>
|
||
</div>
|
||
|
||
{/* Right column - Note */}
|
||
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||
<NoteEditor />
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI Chat - Floating */}
|
||
<FloatingAIChat onEventCreated={handleAIEventCreated} />
|
||
|
||
{/* Add/Edit Event Modal */}
|
||
<Modal
|
||
opened={opened}
|
||
onClose={close}
|
||
title={
|
||
<Text
|
||
fw={400}
|
||
style={{
|
||
letterSpacing: '0.1em',
|
||
fontSize: '1rem',
|
||
}}
|
||
>
|
||
{isEdit ? '编辑事件' : '添加事件'}
|
||
</Text>
|
||
}
|
||
size="md"
|
||
styles={{
|
||
header: {
|
||
borderBottom: '1px solid rgba(0,0,0,0.06)',
|
||
},
|
||
body: {
|
||
paddingTop: 20,
|
||
},
|
||
}}
|
||
>
|
||
<Stack gap="md">
|
||
{/* Event type */}
|
||
<Select
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
|
||
类型
|
||
</Text>
|
||
}
|
||
data={[
|
||
{ value: 'anniversary', label: '纪念日' },
|
||
{ value: 'reminder', label: '提醒' },
|
||
]}
|
||
value={formType}
|
||
onChange={(value) => value && setFormType(value as EventType)}
|
||
readOnly={isEdit}
|
||
rightSectionPointerEvents={isEdit ? 'none' : 'auto'}
|
||
styles={{
|
||
input: {
|
||
borderRadius: 2,
|
||
background: '#faf9f7',
|
||
pointerEvents: isEdit ? 'none' : 'auto',
|
||
},
|
||
}}
|
||
/>
|
||
|
||
{/* Title */}
|
||
<TextInput
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||
标题<Box component="span" c="red">*</Box>
|
||
</Text>
|
||
}
|
||
placeholder="输入标题"
|
||
value={formTitle}
|
||
onChange={(e) => setFormTitle(e.target.value)}
|
||
styles={{
|
||
input: {
|
||
borderRadius: 2,
|
||
background: '#faf9f7',
|
||
},
|
||
}}
|
||
/>
|
||
|
||
{/* Content (only for reminders) */}
|
||
{formType === 'reminder' && (
|
||
<Box style={{ position: 'relative' }}>
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
|
||
内容
|
||
</Text>
|
||
<Box
|
||
style={{
|
||
position: 'relative',
|
||
background: '#faf9f7',
|
||
borderRadius: 2,
|
||
border: '1px solid #ddd',
|
||
}}
|
||
>
|
||
<textarea
|
||
id="content-input"
|
||
placeholder="输入详细内容"
|
||
value={formContent}
|
||
onChange={(e) => setFormContent(e.target.value)}
|
||
rows={3}
|
||
style={{
|
||
width: '100%',
|
||
border: 'none',
|
||
background: 'transparent',
|
||
padding: '8px 12px',
|
||
fontSize: '14px',
|
||
resize: 'vertical',
|
||
fontFamily: 'inherit',
|
||
outline: 'none',
|
||
minHeight: '72px',
|
||
boxSizing: 'border-box',
|
||
}}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
)}
|
||
|
||
{/* Date & Time - Combined selector */}
|
||
<Box>
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
|
||
日期
|
||
</Text>
|
||
<Group gap={8} grow>
|
||
{/* Date selector - Fixed 6-row calendar */}
|
||
<Popover
|
||
position="bottom"
|
||
withArrow
|
||
shadow="md"
|
||
>
|
||
<Popover.Target>
|
||
<TextInput
|
||
placeholder="选择日期"
|
||
value={formDate ? `${formDate.getFullYear()}-${String(formDate.getMonth() + 1).padStart(2, '0')}-${String(formDate.getDate()).padStart(2, '0')}` : ''}
|
||
readOnly
|
||
leftSection={<IconCalendar size={14} />}
|
||
leftSectionPointerEvents="none"
|
||
styles={{
|
||
input: {
|
||
borderRadius: 2,
|
||
background: '#faf9f7',
|
||
borderColor: '#ddd',
|
||
color: '#666',
|
||
height: 32,
|
||
paddingLeft: 36,
|
||
paddingRight: 8,
|
||
cursor: 'pointer',
|
||
},
|
||
section: {
|
||
paddingLeft: 8,
|
||
},
|
||
}}
|
||
/>
|
||
</Popover.Target>
|
||
<Popover.Dropdown style={{ background: 'transparent', border: 'none', padding: 0, boxShadow: 'none' }}>
|
||
<FixedCalendar
|
||
value={formDate}
|
||
onChange={(date) => setFormDate(date)}
|
||
/>
|
||
</Popover.Dropdown>
|
||
</Popover>
|
||
|
||
{/* Time selector (only for reminders) - Popover picker */}
|
||
{formType === 'reminder' && (
|
||
<PopoverTimePicker
|
||
value={formTime}
|
||
onChange={(time) => setFormTime(time)}
|
||
size="sm"
|
||
/>
|
||
)}
|
||
</Group>
|
||
</Box>
|
||
|
||
{/* Lunar switch (only for anniversaries) */}
|
||
{formType === 'anniversary' && (
|
||
<Switch
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||
农历日期
|
||
</Text>
|
||
}
|
||
checked={formIsLunar}
|
||
onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
|
||
/>
|
||
)}
|
||
|
||
{/* Repeat and Reminder on same row */}
|
||
<Group gap={12} grow>
|
||
{/* Repeat type */}
|
||
<Select
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||
重复
|
||
</Text>
|
||
}
|
||
data={[
|
||
{ value: 'none', label: '不重复' },
|
||
{ value: 'daily', label: '每天' },
|
||
{ value: 'weekly', label: '每周' },
|
||
{ value: 'monthly', label: '每月' },
|
||
{ value: 'yearly', label: '每年' },
|
||
]}
|
||
value={formRepeatType}
|
||
onChange={(value) => value && setFormRepeatType(value as RepeatType)}
|
||
styles={{
|
||
input: {
|
||
borderRadius: 2,
|
||
background: '#faf9f7',
|
||
},
|
||
}}
|
||
/>
|
||
|
||
{/* Reminder options (only for reminders) */}
|
||
{formType === 'reminder' && (
|
||
<Select
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||
提醒
|
||
<Box component="span" size="xs" c="dimmed" ml={4}>
|
||
{formDate ? formatReminderTimeDisplay(
|
||
calculateReminderTimes(
|
||
`${formDate.getFullYear()}-${String(formDate.getMonth() + 1).padStart(2, '0')}-${String(formDate.getDate()).padStart(2, '0')}T${formTime ? formTime : '00:00'}:00`,
|
||
!!formTime,
|
||
formReminderValue
|
||
)
|
||
) : ''}
|
||
</Box>
|
||
</Text>
|
||
}
|
||
data={getReminderOptions(!!formTime).map(opt => ({
|
||
value: opt.value,
|
||
label: opt.label,
|
||
}))}
|
||
value={formReminderValue}
|
||
onChange={(value) => value && setFormReminderValue(value)}
|
||
styles={{
|
||
input: {
|
||
borderRadius: 2,
|
||
background: '#faf9f7',
|
||
},
|
||
}}
|
||
/>
|
||
)}
|
||
</Group>
|
||
|
||
{/* Color (only for reminders) */}
|
||
{formType === 'reminder' && (
|
||
<Box>
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
|
||
颜色
|
||
</Text>
|
||
<Group gap={8}>
|
||
{([
|
||
{ value: 'none' as const, color: 'rgba(0, 0, 0, 0.15)' },
|
||
{ value: 'red' as const, color: '#dc2626' },
|
||
{ value: 'green' as const, color: '#16a34a' },
|
||
{ value: 'yellow' as const, color: '#ca8a04' },
|
||
].map((item) => (
|
||
<Box
|
||
key={item.value}
|
||
onClick={() => setFormPriority(item.value)}
|
||
style={{
|
||
width: 24,
|
||
height: 24,
|
||
borderRadius: '50%',
|
||
background: item.color,
|
||
border: formPriority === item.value ? '2px solid #1a1a1a' : '1px solid rgba(0, 0, 0, 0.1)',
|
||
cursor: 'pointer',
|
||
transition: 'all 0.2s ease',
|
||
transform: formPriority === item.value ? 'scale(1.1)' : 'scale(1)',
|
||
}}
|
||
/>
|
||
)))}
|
||
</Group>
|
||
</Box>
|
||
)}
|
||
|
||
{/* Holiday switch (only for anniversaries) */}
|
||
{formType === 'anniversary' && (
|
||
<Switch
|
||
label={
|
||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||
节假日
|
||
</Text>
|
||
}
|
||
checked={formIsHoliday}
|
||
onChange={(e) => setFormIsHoliday(e.currentTarget.checked)}
|
||
/>
|
||
)}
|
||
|
||
{/* Actions */}
|
||
<Group justify="space-between" mt="md">
|
||
{isEdit && (
|
||
<Button
|
||
color="dark"
|
||
variant="light"
|
||
onClick={handleDeleteFromModal}
|
||
style={{
|
||
borderRadius: 2,
|
||
}}
|
||
>
|
||
删除
|
||
</Button>
|
||
)}
|
||
<Group ml="auto">
|
||
<Button
|
||
variant="subtle"
|
||
onClick={close}
|
||
style={{
|
||
borderRadius: 2,
|
||
color: '#666',
|
||
}}
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
onClick={handleSubmit}
|
||
disabled={!formTitle.trim() || !formDate}
|
||
style={{
|
||
background: '#1a1a1a',
|
||
border: '1px solid #1a1a1a',
|
||
borderRadius: 2,
|
||
}}
|
||
>
|
||
{isEdit ? '保存' : '添加'}
|
||
</Button>
|
||
</Group>
|
||
</Group>
|
||
</Stack>
|
||
</Modal>
|
||
</Container>
|
||
</div>
|
||
);
|
||
}
|