Kyle Pope 8945295e2a Replace Sheet overlays with inline detail panels across all pages
- Calendar: move view selector left, inline EventDetailPanel with view/edit/create
  modes, fix resize on panel close, remove all Sheet/Dialog usage
- Todos: add TodoDetailPanel with inline view/edit/create, replace CategoryFilterBar
  with shared component (drag-and-drop categories), 55/45 split layout
- Reminders: add ReminderDetailPanel with inline view/edit/create, 55/45 split layout
- Dashboard: all widget items now deep-link to destination page AND open the
  relevant item's detail panel (events, todos, reminders)
- Fix TS errors: unused imports, undefined→null coalescing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:43:06 +08:00

81 lines
2.7 KiB
TypeScript

import { format, isPast, endOfDay } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { CheckCircle2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
interface DashboardTodo {
id: number;
title: string;
due_date: string;
priority: string;
category?: string;
}
interface TodoWidgetProps {
todos: DashboardTodo[];
}
const priorityColors: Record<string, string> = {
low: 'bg-green-500/10 text-green-400 border-green-500/20',
medium: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',
high: 'bg-red-500/10 text-red-400 border-red-500/20',
};
const dotColors: Record<string, string> = {
high: 'bg-red-400',
medium: 'bg-yellow-400',
low: 'bg-green-400',
};
export default function TodoWidget({ todos }: TodoWidgetProps) {
const navigate = useNavigate();
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="p-1.5 rounded-md bg-blue-500/10">
<CheckCircle2 className="h-4 w-4 text-blue-400" />
</div>
Upcoming Todos
</CardTitle>
</CardHeader>
<CardContent>
{todos.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-6">
All caught up.
</p>
) : (
<div className="space-y-0.5">
{todos.slice(0, 5).map((todo) => {
const isOverdue = isPast(endOfDay(new Date(todo.due_date)));
return (
<div
key={todo.id}
onClick={() => navigate('/todos', { state: { todoId: todo.id } })}
className="flex items-center gap-2 py-1.5 px-2 rounded-md hover:bg-white/5 transition-colors duration-150 cursor-pointer"
>
<div className={cn('w-1.5 h-1.5 rounded-full shrink-0', dotColors[todo.priority] || dotColors.medium)} />
<span className="text-sm font-medium truncate flex-1 min-w-0">{todo.title}</span>
<span className={cn(
'text-xs shrink-0 whitespace-nowrap',
isOverdue ? 'text-red-400' : 'text-muted-foreground'
)}>
{format(new Date(todo.due_date), 'MMM d')}
{isOverdue && ' overdue'}
</span>
<Badge className={cn('text-[9px] shrink-0 py-0', priorityColors[todo.priority] || priorityColors.medium)}>
{todo.priority}
</Badge>
</div>
);
})}
</div>
)}
</CardContent>
</Card>
);
}