- Compact h-16 header with segmented filter, search input - Stat cards (Active/Overdue/Dismissed) with semantic colors - New ReminderItem component: single-line rows with grouped sections - Optimistic delete, 2-second confirm pattern, dismiss action - Mark Stage 4 Reminders as completed in ui_refresh.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Bell } from 'lucide-react';
|
|
import { parseISO, isPast, isToday, compareAsc } from 'date-fns';
|
|
import type { Reminder } from '@/types';
|
|
import { EmptyState } from '@/components/ui/empty-state';
|
|
import ReminderItem from './ReminderItem';
|
|
|
|
interface ReminderListProps {
|
|
reminders: Reminder[];
|
|
onEdit: (reminder: Reminder) => void;
|
|
onAdd: () => void;
|
|
}
|
|
|
|
interface ReminderGroup {
|
|
key: string;
|
|
label: string;
|
|
reminders: Reminder[];
|
|
}
|
|
|
|
function sortByRemindAt(a: Reminder, b: Reminder): number {
|
|
if (!a.remind_at && !b.remind_at) return 0;
|
|
if (!a.remind_at) return 1;
|
|
if (!b.remind_at) return -1;
|
|
return compareAsc(parseISO(a.remind_at), parseISO(b.remind_at));
|
|
}
|
|
|
|
export default function ReminderList({ reminders, onEdit, onAdd }: ReminderListProps) {
|
|
const groups = useMemo(() => {
|
|
const overdue: Reminder[] = [];
|
|
const today: Reminder[] = [];
|
|
const upcoming: Reminder[] = [];
|
|
const noDate: Reminder[] = [];
|
|
const dismissed: Reminder[] = [];
|
|
|
|
for (const reminder of reminders) {
|
|
if (reminder.is_dismissed) {
|
|
dismissed.push(reminder);
|
|
continue;
|
|
}
|
|
|
|
if (!reminder.remind_at) {
|
|
noDate.push(reminder);
|
|
continue;
|
|
}
|
|
|
|
const date = parseISO(reminder.remind_at);
|
|
if (isToday(date)) {
|
|
today.push(reminder);
|
|
} else if (isPast(date)) {
|
|
overdue.push(reminder);
|
|
} else {
|
|
upcoming.push(reminder);
|
|
}
|
|
}
|
|
|
|
overdue.sort(sortByRemindAt);
|
|
today.sort(sortByRemindAt);
|
|
upcoming.sort(sortByRemindAt);
|
|
|
|
const result: ReminderGroup[] = [];
|
|
if (overdue.length > 0) result.push({ key: 'overdue', label: 'Overdue', reminders: overdue });
|
|
if (today.length > 0) result.push({ key: 'today', label: 'Today', reminders: today });
|
|
if (upcoming.length > 0) result.push({ key: 'upcoming', label: 'Upcoming', reminders: upcoming });
|
|
if (noDate.length > 0) result.push({ key: 'no-date', label: 'No Date', reminders: noDate });
|
|
if (dismissed.length > 0) result.push({ key: 'dismissed', label: 'Dismissed', reminders: dismissed });
|
|
|
|
return result;
|
|
}, [reminders]);
|
|
|
|
if (reminders.length === 0) {
|
|
return (
|
|
<EmptyState
|
|
icon={Bell}
|
|
title="No reminders"
|
|
description="Create a reminder so you never miss an important date or event."
|
|
actionLabel="Add Reminder"
|
|
onAction={onAdd}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (groups.length === 1) {
|
|
return (
|
|
<div className="space-y-0.5">
|
|
{groups[0].reminders.map((reminder) => (
|
|
<ReminderItem key={reminder.id} reminder={reminder} onEdit={onEdit} />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
{groups.map((group) => (
|
|
<div key={group.key}>
|
|
<h3 className="text-xs uppercase tracking-wider text-muted-foreground mb-2 px-1">
|
|
{group.label}
|
|
<span className="ml-1.5 tabular-nums">({group.reminders.length})</span>
|
|
</h3>
|
|
<div className="space-y-0.5">
|
|
{group.reminders.map((reminder) => (
|
|
<ReminderItem key={reminder.id} reminder={reminder} onEdit={onEdit} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|