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 { useMemo, useState, useEffect } from 'react';
|
||||||
import { Paper, Text, Checkbox, Group, Stack, ActionIcon } from '@mantine/core';
|
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';
|
import type { Event } from '../../types';
|
||||||
|
|
||||||
interface ReminderCardProps {
|
interface ReminderCardProps {
|
||||||
@ -8,30 +8,69 @@ interface ReminderCardProps {
|
|||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
|
onPostpone?: () => void;
|
||||||
isMissed?: boolean;
|
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 isCompleted = event.is_completed ?? false;
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [isAnimating, setIsAnimating] = useState(false);
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
|
||||||
// 计算时间信息
|
// 计算时间信息
|
||||||
const timeInfo = useMemo(() => {
|
const timeInfo = useMemo(() => {
|
||||||
|
// 处理空日期情况
|
||||||
|
if (!event.date) {
|
||||||
|
return { isPast: false, isToday: true, timeStr: '未设置时间', diff: 0, hasTime: false };
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date();
|
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 diff = eventDate.getTime() - now.getTime();
|
||||||
const isPast = diff < 0;
|
const isPast = diff < 0;
|
||||||
const isToday = eventDate.toDateString() === now.toDateString();
|
const isToday = eventDate.toDateString() === now.toDateString();
|
||||||
|
|
||||||
const timeStr = eventDate.toLocaleString('zh-CN', {
|
// 格式化显示
|
||||||
month: 'numeric',
|
let timeStr: string;
|
||||||
day: 'numeric',
|
// 优先使用 hasExplicitTime(本地格式),如果没有显式时间才检查 ISO 格式
|
||||||
hour: '2-digit',
|
if (hasExplicitTime || isoHasTime) {
|
||||||
minute: '2-digit',
|
// 有设置时间,显示日期+时间
|
||||||
});
|
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]);
|
}, [event.date]);
|
||||||
|
|
||||||
// 获取文字颜色
|
// 获取文字颜色
|
||||||
@ -164,6 +203,26 @@ export function ReminderCard({ event, onToggle, onClick, isMissed = false }: Rem
|
|||||||
|
|
||||||
{/* Quick actions */}
|
{/* Quick actions */}
|
||||||
<Group gap={4}>
|
<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
|
<ActionIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|||||||
@ -21,6 +21,7 @@ interface ReminderListProps {
|
|||||||
onAddClick: () => void;
|
onAddClick: () => void;
|
||||||
onDelete?: (event: Event) => void;
|
onDelete?: (event: Event) => void;
|
||||||
onRestore?: (event: Event) => void;
|
onRestore?: (event: Event) => void;
|
||||||
|
onPostpone?: (event: Event) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReminderList({
|
export function ReminderList({
|
||||||
@ -30,18 +31,20 @@ export function ReminderList({
|
|||||||
onAddClick,
|
onAddClick,
|
||||||
onDelete,
|
onDelete,
|
||||||
onRestore,
|
onRestore,
|
||||||
|
onPostpone,
|
||||||
}: ReminderListProps) {
|
}: ReminderListProps) {
|
||||||
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
// 仅按日期部分判断,获取今天的开始时间
|
||||||
today.setHours(0, 0, 0, 0);
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
const tomorrow = new Date(today);
|
todayStart.setHours(0, 0, 0, 0);
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
const tomorrowStart = new Date(todayStart);
|
||||||
tomorrow.setHours(0, 0, 0, 0);
|
tomorrowStart.setDate(tomorrowStart.getDate() + 1);
|
||||||
const dayAfterTomorrow = new Date(tomorrow);
|
tomorrowStart.setHours(0, 0, 0, 0);
|
||||||
dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1);
|
const dayAfterTomorrowStart = new Date(tomorrowStart);
|
||||||
dayAfterTomorrow.setHours(0, 0, 0, 0);
|
dayAfterTomorrowStart.setDate(dayAfterTomorrowStart.getDate() + 1);
|
||||||
|
dayAfterTomorrowStart.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const reminders = events.filter((e) => e.type === 'reminder');
|
const reminders = events.filter((e) => e.type === 'reminder');
|
||||||
|
|
||||||
@ -53,21 +56,29 @@ export function ReminderList({
|
|||||||
};
|
};
|
||||||
|
|
||||||
reminders.forEach((event) => {
|
reminders.forEach((event) => {
|
||||||
const eventDate = new Date(event.date);
|
// 无日期的提醒不视为逾期,放入今天分组
|
||||||
|
if (!event.date) {
|
||||||
// 已过期且已完成的去归档页
|
result.today.push(event);
|
||||||
if (event.is_completed && eventDate < now) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未过期或已完成未过期的,按时间分组
|
const eventDate = new Date(event.date);
|
||||||
if (eventDate < now) {
|
// 仅取日期部分进行比较(忽略时间)
|
||||||
|
const eventDateOnly = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate());
|
||||||
|
|
||||||
|
// 已过期且已完成的去归档页
|
||||||
|
if (event.is_completed && eventDateOnly < todayStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按日期部分判断是否逾期
|
||||||
|
if (eventDateOnly < todayStart) {
|
||||||
// 已过期未完成
|
// 已过期未完成
|
||||||
result.missed.push(event);
|
result.missed.push(event);
|
||||||
} else if (eventDate < tomorrow) {
|
} else if (eventDateOnly < tomorrowStart) {
|
||||||
// 今天
|
// 今天
|
||||||
result.today.push(event);
|
result.today.push(event);
|
||||||
} else if (eventDate < dayAfterTomorrow) {
|
} else if (eventDateOnly < dayAfterTomorrowStart) {
|
||||||
// 明天
|
// 明天
|
||||||
result.tomorrow.push(event);
|
result.tomorrow.push(event);
|
||||||
} else {
|
} else {
|
||||||
@ -176,6 +187,7 @@ export function ReminderList({
|
|||||||
onClick={() => onEventClick(event)}
|
onClick={() => onEventClick(event)}
|
||||||
onToggle={() => onToggleComplete(event)}
|
onToggle={() => onToggleComplete(event)}
|
||||||
onDelete={onDelete ? () => onDelete(event) : undefined}
|
onDelete={onDelete ? () => onDelete(event) : undefined}
|
||||||
|
onPostpone={onPostpone ? () => onPostpone(event) : undefined}
|
||||||
isMissed={true}
|
isMissed={true}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -35,10 +35,14 @@ export function ArchivePage() {
|
|||||||
// 获取已归档的提醒(已过期且已勾选的)
|
// 获取已归档的提醒(已过期且已勾选的)
|
||||||
const archivedReminders = events.filter((e) => {
|
const archivedReminders = events.filter((e) => {
|
||||||
if (e.type !== 'reminder' || !e.is_completed) return false;
|
if (e.type !== 'reminder' || !e.is_completed) return false;
|
||||||
|
if (!e.date) return false; // 跳过无日期的
|
||||||
const eventDate = new Date(e.date);
|
const eventDate = new Date(e.date);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return eventDate < now; // 仅已过期的
|
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) => {
|
const handleRestore = async (event: Event) => {
|
||||||
await updateEventById(event.id, { is_completed: false });
|
await updateEventById(event.id, { is_completed: false });
|
||||||
|
|||||||
@ -69,6 +69,11 @@ export function HomePage() {
|
|||||||
setFormIsLunar(event.is_lunar);
|
setFormIsLunar(event.is_lunar);
|
||||||
setFormRepeatType(event.repeat_type);
|
setFormRepeatType(event.repeat_type);
|
||||||
setFormIsHoliday(event.is_holiday || false);
|
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();
|
open();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,15 +97,26 @@ export function HomePage() {
|
|||||||
// 确保 date 是 Date 对象
|
// 确保 date 是 Date 对象
|
||||||
const dateObj = formDate instanceof Date ? formDate : new Date(formDate as unknown as string);
|
const dateObj = formDate instanceof Date ? formDate : new Date(formDate as unknown as string);
|
||||||
|
|
||||||
const dateStr = formTime
|
let dateStr: string;
|
||||||
? new Date(dateObj.setHours(parseInt(formTime.split(':')[0]), parseInt(formTime.split(':')[1])))
|
const year = dateObj.getFullYear();
|
||||||
: dateObj;
|
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 = {
|
const eventData = {
|
||||||
type: formType,
|
type: formType,
|
||||||
title: formTitle,
|
title: formTitle,
|
||||||
content: formContent || undefined,
|
content: formContent || undefined,
|
||||||
date: dateStr.toISOString(),
|
date: dateStr,
|
||||||
is_lunar: formIsLunar,
|
is_lunar: formIsLunar,
|
||||||
repeat_type: formRepeatType,
|
repeat_type: formRepeatType,
|
||||||
is_holiday: formIsHoliday || undefined,
|
is_holiday: formIsHoliday || undefined,
|
||||||
@ -151,6 +167,27 @@ export function HomePage() {
|
|||||||
fetchEvents();
|
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 = () => {
|
const resetForm = () => {
|
||||||
setFormType('anniversary');
|
setFormType('anniversary');
|
||||||
setFormTitle('');
|
setFormTitle('');
|
||||||
@ -264,6 +301,7 @@ export function HomePage() {
|
|||||||
onAddClick={() => handleAddClick('reminder')}
|
onAddClick={() => handleAddClick('reminder')}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onRestore={handleRestore}
|
onRestore={handleRestore}
|
||||||
|
onPostpone={handlePostpone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user