diff --git a/src/components/reminder/ReminderCard.tsx b/src/components/reminder/ReminderCard.tsx index 7b1c28a..57f5d5d 100644 --- a/src/components/reminder/ReminderCard.tsx +++ b/src/components/reminder/ReminderCard.tsx @@ -1,6 +1,6 @@ import { useMemo, useState, useEffect } from 'react'; import { Paper, Text, Checkbox, Group, Stack, ActionIcon } from '@mantine/core'; -import { IconDots } from '@tabler/icons-react'; +import { IconDots, IconArrowForward } from '@tabler/icons-react'; import type { Event } from '../../types'; interface ReminderCardProps { @@ -8,30 +8,69 @@ interface ReminderCardProps { onToggle: () => void; onClick: () => void; onDelete?: () => void; + onPostpone?: () => void; isMissed?: boolean; } -export function ReminderCard({ event, onToggle, onClick, isMissed = false }: ReminderCardProps) { +export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, isMissed = false }: ReminderCardProps) { const isCompleted = event.is_completed ?? false; const [isHovered, setIsHovered] = useState(false); const [isAnimating, setIsAnimating] = useState(false); // 计算时间信息 const timeInfo = useMemo(() => { + // 处理空日期情况 + if (!event.date) { + return { isPast: false, isToday: true, timeStr: '未设置时间', diff: 0, hasTime: false }; + } + const now = new Date(); - const eventDate = new Date(event.date); + // 解析日期,处理本地时间格式(无时区)和ISO格式 + let eventDate: Date; + const dateStr = event.date as string; + // 如果是本地时间格式(YYYY-MM-DDTHH:mm:ss),添加时区信息避免UTC转换 + if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(dateStr)) { + // 本地时间格式,直接使用 + eventDate = new Date(dateStr); + } else { + // ISO格式,可能有时区 + eventDate = new Date(dateStr); + } + + // 判断是否是有意设置的时间(不是默认的00:00) + // 对于本地格式的日期,检查日期字符串中是否包含非00:00的时间 + const timeMatch = dateStr.match(/T(\d{2}):(\d{2})/); + const hasExplicitTime = timeMatch && (timeMatch[1] !== '00' || timeMatch[2] !== '00'); + // 对于ISO格式,检查小时和分钟 + const hours = eventDate.getHours(); + const minutes = eventDate.getMinutes(); + const isoHasTime = hours !== 0 || minutes !== 0; + const diff = eventDate.getTime() - now.getTime(); const isPast = diff < 0; const isToday = eventDate.toDateString() === now.toDateString(); - const timeStr = eventDate.toLocaleString('zh-CN', { - month: 'numeric', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); + // 格式化显示 + let timeStr: string; + // 优先使用 hasExplicitTime(本地格式),如果没有显式时间才检查 ISO 格式 + if (hasExplicitTime || isoHasTime) { + // 有设置时间,显示日期+时间 + timeStr = eventDate.toLocaleString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + } else { + // 没有设置时间,只显示日期 + timeStr = eventDate.toLocaleString('zh-CN', { + month: 'numeric', + day: 'numeric', + }); + } - return { isPast, isToday, timeStr, diff }; + return { isPast, isToday, timeStr, diff, hasTime: hasExplicitTime || isoHasTime }; }, [event.date]); // 获取文字颜色 @@ -164,6 +203,26 @@ export function ReminderCard({ event, onToggle, onClick, isMissed = false }: Rem {/* Quick actions */} + {/* 顺延按钮 - 仅在逾期状态显示 */} + {isMissed && onPostpone && ( + { + e.stopPropagation(); + onPostpone(); + }} + style={{ + color: '#e67e22', + opacity: isHovered ? 1 : 0, + transition: 'all 0.2s ease', + }} + title="顺延到今天" + > + + + )} void; onDelete?: (event: Event) => void; onRestore?: (event: Event) => void; + onPostpone?: (event: Event) => void; } export function ReminderList({ @@ -30,18 +31,20 @@ export function ReminderList({ onAddClick, onDelete, onRestore, + onPostpone, }: ReminderListProps) { const grouped = useMemo(() => { const now = new Date(); - const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - today.setHours(0, 0, 0, 0); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); - tomorrow.setHours(0, 0, 0, 0); - const dayAfterTomorrow = new Date(tomorrow); - dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1); - dayAfterTomorrow.setHours(0, 0, 0, 0); + // 仅按日期部分判断,获取今天的开始时间 + const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + todayStart.setHours(0, 0, 0, 0); + const tomorrowStart = new Date(todayStart); + tomorrowStart.setDate(tomorrowStart.getDate() + 1); + tomorrowStart.setHours(0, 0, 0, 0); + const dayAfterTomorrowStart = new Date(tomorrowStart); + dayAfterTomorrowStart.setDate(dayAfterTomorrowStart.getDate() + 1); + dayAfterTomorrowStart.setHours(0, 0, 0, 0); const reminders = events.filter((e) => e.type === 'reminder'); @@ -53,21 +56,29 @@ export function ReminderList({ }; reminders.forEach((event) => { - const eventDate = new Date(event.date); - - // 已过期且已完成的去归档页 - if (event.is_completed && eventDate < now) { + // 无日期的提醒不视为逾期,放入今天分组 + if (!event.date) { + result.today.push(event); return; } - // 未过期或已完成未过期的,按时间分组 - if (eventDate < now) { + const eventDate = new Date(event.date); + // 仅取日期部分进行比较(忽略时间) + const eventDateOnly = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate()); + + // 已过期且已完成的去归档页 + if (event.is_completed && eventDateOnly < todayStart) { + return; + } + + // 按日期部分判断是否逾期 + if (eventDateOnly < todayStart) { // 已过期未完成 result.missed.push(event); - } else if (eventDate < tomorrow) { + } else if (eventDateOnly < tomorrowStart) { // 今天 result.today.push(event); - } else if (eventDate < dayAfterTomorrow) { + } else if (eventDateOnly < dayAfterTomorrowStart) { // 明天 result.tomorrow.push(event); } else { @@ -176,6 +187,7 @@ export function ReminderList({ onClick={() => onEventClick(event)} onToggle={() => onToggleComplete(event)} onDelete={onDelete ? () => onDelete(event) : undefined} + onPostpone={onPostpone ? () => onPostpone(event) : undefined} isMissed={true} /> ))} diff --git a/src/pages/ArchivePage.tsx b/src/pages/ArchivePage.tsx index bce8ed4..34d7fab 100644 --- a/src/pages/ArchivePage.tsx +++ b/src/pages/ArchivePage.tsx @@ -35,10 +35,14 @@ export function ArchivePage() { // 获取已归档的提醒(已过期且已勾选的) const archivedReminders = events.filter((e) => { if (e.type !== 'reminder' || !e.is_completed) return false; + if (!e.date) return false; // 跳过无日期的 const eventDate = new Date(e.date); const now = new Date(); return eventDate < now; // 仅已过期的 - }).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }).sort((a, b) => { + if (!a.date || !b.date) return 0; + return new Date(b.date).getTime() - new Date(a.date).getTime(); + }); const handleRestore = async (event: Event) => { await updateEventById(event.id, { is_completed: false }); diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 98e0828..d080e88 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -69,6 +69,11 @@ export function HomePage() { setFormIsLunar(event.is_lunar); setFormRepeatType(event.repeat_type); setFormIsHoliday(event.is_holiday || false); + // 提取时间(如果日期中包含时间信息) + const eventDate = new Date(event.date); + const hours = eventDate.getHours(); + const minutes = eventDate.getMinutes(); + setFormTime(hours === 0 && minutes === 0 ? '' : `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`); open(); }; @@ -92,15 +97,26 @@ export function HomePage() { // 确保 date 是 Date 对象 const dateObj = formDate instanceof Date ? formDate : new Date(formDate as unknown as string); - const dateStr = formTime - ? new Date(dateObj.setHours(parseInt(formTime.split(':')[0]), parseInt(formTime.split(':')[1]))) - : dateObj; + 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) { + // 有时间:构建本地时间格式 + const hours = String(parseInt(formTime.split(':')[0])).padStart(2, '0'); + const minutes = String(parseInt(formTime.split(':')[1])).padStart(2, '0'); + dateStr = `${year}-${month}-${day}T${hours}:${minutes}:00`; + } else { + // 无时间:只保存日期部分 + dateStr = `${year}-${month}-${day}T00:00:00`; + } const eventData = { type: formType, title: formTitle, content: formContent || undefined, - date: dateStr.toISOString(), + date: dateStr, is_lunar: formIsLunar, repeat_type: formRepeatType, is_holiday: formIsHoliday || undefined, @@ -151,6 +167,27 @@ export function HomePage() { 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 result = await updateEventById(event.id, { + date: newDateStr, + }); + if (result.error) { + console.error('顺延失败:', result.error); + } + }; + const resetForm = () => { setFormType('anniversary'); setFormTitle(''); @@ -264,6 +301,7 @@ export function HomePage() { onAddClick={() => handleAddClick('reminder')} onDelete={handleDelete} onRestore={handleRestore} + onPostpone={handlePostpone} />