feat: 优化提醒时间显示和顺延功能
- 修复时间显示问题:无时间仅显示日期,有时间显示日期+时间 - 修复时区转换问题:使用本地时间格式存储 - 逾期提醒增加【顺延】操作:日期调整为今日,时间不变 - 修复空日期处理和归档页过滤逻辑 Co-Authored-By: Claude (MiniMax-M2.1) <noreply@anthropic.com>
This commit is contained in:
parent
a8b4f17043
commit
7b0afbb27b
@ -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 */}
|
||||
<Group gap={4}>
|
||||
{/* 顺延按钮 - 仅在逾期状态显示 */}
|
||||
{isMissed && onPostpone && (
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
color="orange"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onPostpone();
|
||||
}}
|
||||
style={{
|
||||
color: '#e67e22',
|
||||
opacity: isHovered ? 1 : 0,
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
title="顺延到今天"
|
||||
>
|
||||
<IconArrowForward size={12} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
|
||||
@ -21,6 +21,7 @@ interface ReminderListProps {
|
||||
onAddClick: () => 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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
</div>
|
||||
</Grid.Col>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user