File size: 4,786 Bytes
59bd45e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import React, { useState } from 'react';
import { Circle, CheckCircle2, BookOpen, Briefcase, Coffee, HeartPulse, Calendar, MapPin } from 'lucide-react';
import { TodoItem, TodoCategory } from '../types';
interface TodoCardProps {
item: TodoItem;
index: number;
onToggle: (id: string) => void;
location?: string;
time?: string;
}
export const TodoCard: React.FC<TodoCardProps> = ({ item, index, onToggle, location, time }) => {
const [isDone, setIsDone] = useState(item.isDone);
const handleToggle = (e: React.MouseEvent) => {
e.stopPropagation();
setIsDone(!isDone);
onToggle(item.id);
};
const getCategoryIcon = (category?: TodoCategory) => {
const props = { size: 12, className: "opacity-60" };
switch (category) {
case 'study': return <BookOpen {...props} />;
case 'work': return <Briefcase {...props} />;
case 'life': return <Coffee {...props} />;
case 'health': return <HeartPulse {...props} />;
default: return null;
}
};
const formatScheduledTime = (timestamp?: number) => {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const isToday = date.toDateString() === now.toDateString();
// Simple format: "Today · 6:30 PM"
const timeStr = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
if (isToday) return `Today · ${timeStr}`;
// "Tomorrow" logic could go here, keeping it simple for now
return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })} · ${timeStr}`;
};
return (
<div
className={`
group relative w-full rounded-[22px] p-5 mb-4
border transition-all duration-700 ease-out cursor-pointer
${isDone
? 'bg-white/20 border-white/20 shadow-none opacity-60 grayscale-[0.3]'
: 'bg-white/40 border-white/50 shadow-[0_4px_24px_-8px_rgba(0,0,0,0.02)] hover:bg-white/50'}
`}
style={{
animation: `fadeSlideUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards`,
animationDelay: `${index * 100}ms`,
opacity: 0,
backdropFilter: 'blur(12px)'
}}
>
{/* Inner highlight */}
<div className="absolute inset-0 rounded-[22px] ring-1 ring-inset ring-white/30 pointer-events-none" />
<div className="relative z-10 flex items-start gap-4">
{/* Status Indicator (Left) */}
<button
onClick={handleToggle}
className="mt-0.5 flex-shrink-0 text-slate-400 hover:text-purple-400 transition-colors duration-300"
>
{isDone ? (
<CheckCircle2 size={20} className="text-purple-300 fill-purple-50" strokeWidth={1.5} />
) : (
<Circle size={20} className="opacity-50" strokeWidth={1.5} />
)}
</button>
{/* Content Area */}
<div className="flex-1 flex flex-col gap-3">
{/* Main Title */}
<p className={`
text-[16px] leading-relaxed font-normal font-sans transition-all duration-500
${isDone ? 'text-slate-400 line-through decoration-slate-300/50' : 'text-slate-700/90'}
`}>
{item.title}
</p>
{/* Meta Info (Time, Location & Category) */}
<div className="flex flex-col gap-2">
{/* Time and Location */}
<div className="flex flex-wrap items-center gap-3">
{/* Time */}
{(item.scheduledAt || time) && (
<div className="flex items-center gap-1.5 text-xs text-slate-500">
<Calendar size={14} className="opacity-60" />
<span>
{time || formatScheduledTime(item.scheduledAt)}
</span>
</div>
)}
{/* Location */}
{location && (
<div className="flex items-center gap-1.5 text-xs text-slate-500">
<MapPin size={14} className="opacity-60" />
<span>{location}</span>
</div>
)}
</div>
{/* Category */}
{item.category && (
<div className="flex items-center gap-2">
<div className="px-2 py-1 rounded-full bg-white/30 border border-white/20 flex items-center gap-1.5 text-slate-500">
{getCategoryIcon(item.category)}
<span className="text-xs capitalize">{item.category}</span>
</div>
</div>
)}
</div>
</div>
</div>
<style>{`
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
}; |