84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
import { type Component, createMemo } from "solid-js";
|
|
import { type Task, toggleTask, calculateUrgencyFromDate, setActiveTaskId } from "@/store";
|
|
import { cn } from "@/lib/utils";
|
|
import { CheckCircle2, Circle, Clock, ArrowUpCircle } from "lucide-solid";
|
|
|
|
export const TaskCard: Component<{ task: Task }> = (props) => {
|
|
const urgencyLevel = createMemo(() => calculateUrgencyFromDate(props.task.dueDate));
|
|
|
|
const urgencyColor = () => {
|
|
const u = urgencyLevel();
|
|
if (u >= 8) return "text-red-500";
|
|
if (u >= 6) return "text-orange-500";
|
|
if (u >= 4) return "text-blue-500";
|
|
return "text-muted-foreground";
|
|
};
|
|
|
|
const formattedDate = () => {
|
|
const date = new Date(props.task.dueDate);
|
|
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
};
|
|
|
|
return (
|
|
<div
|
|
class={cn(
|
|
"group flex items-center p-4 bg-card border border-border rounded-2xl transition-all duration-300 hover:shadow-xl hover:shadow-primary/5 hover:-translate-y-0.5 cursor-pointer",
|
|
props.task.completed && "opacity-60"
|
|
)}
|
|
onClick={() => setActiveTaskId(props.task.id)}
|
|
>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); toggleTask(props.task.id); }}
|
|
class="mr-4 text-muted-foreground hover:text-primary transition-colors shrink-0"
|
|
>
|
|
{props.task.completed ? (
|
|
<CheckCircle2 class="text-green-500" size={24} />
|
|
) : (
|
|
<Circle size={24} />
|
|
)}
|
|
</button>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<h3 class={cn(
|
|
"text-base font-semibold truncate transition-all",
|
|
props.task.completed && "line-through"
|
|
)}>
|
|
{props.task.title}
|
|
</h3>
|
|
<div class="flex items-center space-x-4 mt-1">
|
|
{/* Priority */}
|
|
<div class="flex items-center gap-1.5 min-w-[32px]">
|
|
<ArrowUpCircle size={12} class="text-primary opacity-60" />
|
|
<span>P{props.task.priority}</span>
|
|
</div>
|
|
|
|
{/* Due Date + Urgency Slide-out */}
|
|
<div class="relative group/date flex items-center h-6 cursor-help">
|
|
{/* Due Date - Static Layer on Top */}
|
|
<div class="flex items-center space-x-1.5 text-[10px] font-bold text-muted-foreground uppercase tracking-wider bg-card relative z-20 pr-1">
|
|
<Clock size={12} class="text-primary/60" />
|
|
<span>{formattedDate()}</span>
|
|
</div>
|
|
|
|
{/* Urgency Number - Slides out from behind with fade */}
|
|
<div class={cn(
|
|
"absolute left-4 top-0 bottom-0 flex items-center transition-all duration-300 ease-out z-10 opacity-0 group-hover/date:opacity-100 group-hover/date:left-full whitespace-nowrap",
|
|
urgencyColor()
|
|
)}>
|
|
<span class="text-[10px] font-bold ml-2 transition-all duration-300 transform translate-x-[-8px] group-hover/date:translate-x-0 group-hover/date:blur-none blur-[2px]">
|
|
Urgency {urgencyLevel()}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ml-4 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
<button class="p-2 hover:bg-muted rounded-full transition-colors">
|
|
<div class="w-1.5 h-1.5 bg-muted-foreground rounded-full" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|