filter and layout improvements. added created at and modified at date/time stamps

This commit is contained in:
2026-01-31 16:30:57 -06:00
parent 2c83b91e66
commit 0554b6bb6b
6 changed files with 227 additions and 137 deletions
+3 -2
View File
@@ -65,10 +65,11 @@ export const Layout: Component<LayoutProps> = (props) => {
{/* Main Content Area */}
<main class="flex-1 min-w-0 overflow-y-auto overflow-x-hidden pb-32 sm:pb-6 relative scroll-smooth">
<div class="w-full max-w-screen-2xl mx-auto p-3 sm:p-6 lg:p-10 space-y-6 sm:space-y-8">
{/* Global Top Header for Filter/Search */}
<div class="sticky top-0 z-40 flex items-center justify-end py-2 -mx-3 px-3 md:relative md:top-auto md:z-auto md:py-0 md:mx-0 md:px-0 bg-background/80 backdrop-blur-md md:bg-transparent md:backdrop-blur-none">
<Show when={props.currentView.toLowerCase() !== "settings"}>
<div class="sticky top-0 z-40 flex items-center justify-end py-2 -mx-3 px-3 md:relative md:top-auto md:z-auto md:py-0 md:mx-0 md:px-0 bg-background/80 backdrop-blur-md md:bg-transparent md:backdrop-blur-none transition-all duration-300">
<FilterBar />
</div>
</Show>
{props.children}
</div>
+12 -5
View File
@@ -1,7 +1,7 @@
import { type Component, createMemo } from "solid-js";
import { type Task, toggleTask, calculateUrgencyFromDate, setActiveTaskId, store } from "@/store";
import { type Task, toggleTask, calculateUrgencyFromDate, setActiveTaskId, store, copyTask } from "@/store";
import { cn } from "@/lib/utils";
import { CheckCircle2, Circle, Clock, ArrowUpCircle } from "lucide-solid";
import { CheckCircle2, Circle, Clock, ArrowUpCircle, Copy } from "lucide-solid";
import { Badge } from "@/components/ui/badge";
import { For } from "solid-js";
@@ -89,9 +89,16 @@ export const TaskCard: Component<{ task: Task }> = (props) => {
</div>
</div>
<div class="ml-2 sm:ml-4 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<button class="p-1.5 hover:bg-muted rounded-full transition-colors">
<div class="w-1 h-1 bg-muted-foreground rounded-full" />
<div class="ml-2 sm:ml-4 opacity-100 sm:opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<button
class="p-1.5 hover:bg-muted rounded-full transition-colors text-muted-foreground hover:text-primary"
onClick={(e) => {
e.stopPropagation();
copyTask(props.task.id);
}}
title="Duplicate Task"
>
<Copy size={16} />
</button>
</div>
</div>
+128 -67
View File
@@ -1,8 +1,8 @@
import { type Component, createEffect, createSignal, For } from "solid-js";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { type Task, removeTask, restoreTask, updateTask, copyTask } from "@/store";
import { type Task, removeTask, restoreTask, updateTask } from "@/store";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, Copy } from "lucide-solid";
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X } from "lucide-solid";
import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
import { Button } from "./ui/button";
import { TagPicker } from "./TagPicker";
@@ -46,7 +46,6 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
const updateTitle = (val: string) => {
setTitle(val);
// Debouncing could be added here, but direct update is fine for now if PB handles it well
updateTask(props.task.id, { title: val });
};
@@ -82,7 +81,7 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
return (
<Sheet open={props.isOpen} onOpenChange={(open) => !open && props.onClose()}>
<SheetContent class="w-full sm:max-w-2xl px-0 sm:px-0 flex flex-col h-full bg-background border-l border-border shadow-2xl">
<SheetContent hideCloseButton class="w-full sm:max-w-2xl px-0 sm:px-0 flex flex-col h-full bg-background border-l border-border shadow-2xl">
{/* Header Area */}
<div class="px-6 pt-6 pb-2 shrink-0">
<textarea
@@ -107,7 +106,7 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
/>
{/* Properties Bar */}
<div class="flex flex-wrap items-center gap-1.5 sm:gap-4 mt-2 text-sm">
<div class="flex flex-wrap items-center gap-2 sm:gap-4 mt-2 text-sm">
{/* Priority */}
<div class="flex items-center gap-1 group shrink-0">
<span class="text-[10px] uppercase font-bold tracking-wider text-muted-foreground/70 hidden sm:inline">Priority</span>
@@ -146,7 +145,6 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
</SelectTrigger>
<SelectContent />
</Select>
</div>
{/* Commands Shortcut */}
@@ -166,67 +164,59 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
<span class="hidden sm:inline">Commands</span>
</Button>
</div>
</div>
{/* Date - Editable */}
<div class="flex items-center gap-1 text-muted-foreground group relative shrink-0 min-w-0 flex-1 sm:flex-initial">
<Calendar size={14} class="group-hover:text-primary transition-colors shrink-0" />
{/* Metadata Row (Dates/Timestamps) */}
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 mt-4 px-1">
<MetadataItem
label="Due"
date={new Date(props.task.dueDate).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
time={new Date(props.task.dueDate).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })}
icon={<Calendar size={12} />}
editable={true}
onClick={() => (document.getElementById('task-detail-date') as HTMLInputElement)?.showPicker?.()}
>
<input
id="task-detail-date"
name="due-date"
type="datetime-local"
value={dateString()}
onInput={onDateInputChange}
onClick={(e) => (e.target as HTMLInputElement).showPicker?.()}
onKeyDown={(e) => {
if (e.key !== "Tab" && e.key !== "Escape") {
e.preventDefault();
}
}}
step="900"
class="bg-transparent border-none p-0 text-[11px] sm:text-xs font-medium focus:ring-0 focus:outline-none cursor-pointer hover:text-foreground appearance-none min-w-[125px] w-full"
title="Change Due Date"
class="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
/>
</MetadataItem>
<MetadataItem
label="Created"
date={new Date(props.task.created).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
time={new Date(props.task.created).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })}
/>
<MetadataItem
label="Updated"
date={new Date(props.task.updated).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
time={new Date(props.task.updated).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })}
/>
</div>
{/* Copy button */}
<div class="flex items-center gap-1 shrink-0 ml-auto">
<Button
variant="ghost"
size="sm"
class="h-8 w-8 px-0 hover:bg-muted/50 text-muted-foreground transition-colors"
onClick={() => copyTask(props.task.id)}
>
<Copy size={16} />
</Button>
<div class="h-px w-full bg-border mt-5" />
</div>
{/* Delete Button */}
<div class="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="sm"
class="h-8 w-8 px-0 hover:bg-red-500/10 hover:text-red-500 text-muted-foreground transition-colors"
onClick={() => {
const taskId = props.task.id;
removeTask(taskId);
props.onClose();
toast.info("Task moved to trash. Find inside Settings.", {
action: {
label: "Undo",
onClick: () => restoreTask(taskId)
}
});
}}
>
<Trash2 size={16} />
</Button>
</div>
</div>
{/* Content Area */}
<div class="flex-1 overflow-y-auto px-6 py-4 pb-20 flex flex-col gap-6">
<Suspense fallback={<div class="h-32 animate-pulse bg-muted/20 rounded-lg" />}>
<TaskEditor
content={props.task.content}
onUpdate={updateTaskContent}
onEditorReady={setEditorInstance}
/>
</Suspense>
{/* Tags Row */}
<div class="flex flex-wrap items-center gap-2 mt-4 pb-1">
<span class="text-[10px] uppercase font-bold tracking-wider text-muted-foreground/70 shrink-0">Tags</span>
<div class="flex flex-wrap items-center gap-1.5 min-w-0">
{/* Tags at the bottom */}
<div class="mt-auto pt-6 border-t border-border/50">
<div class="flex flex-wrap items-center gap-3">
<span class="text-[10px] uppercase font-bold tracking-wider text-muted-foreground/60 shrink-0">Tags</span>
<div class="flex flex-wrap items-center gap-1.5 min-w-0 flex-1">
<TagPicker
selectedTags={props.task.tags || []}
onTagsChange={updateTags}
@@ -236,7 +226,7 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
{(tag) => (
<Badge
variant="secondary"
class="h-7 px-2 text-xs gap-1 bg-muted/40 border-border/50 hover:bg-destructive/10 hover:border-destructive/30 cursor-pointer group/tag transition-all whitespace-nowrap shrink-0"
class="h-7 px-2 text-xs gap-1.5 bg-muted/30 border-border/50 hover:bg-destructive/10 hover:border-destructive/30 cursor-pointer group/tag transition-all whitespace-nowrap shrink-0"
onClick={() => updateTags((props.task.tags || []).filter(t => t !== tag))}
>
<div class={cn("w-1.5 h-1.5 rounded-full transition-colors group-hover/tag:bg-destructive", (store.tagDefinitions?.[tag] ?? 5) > 5 ? "bg-green-500" : (store.tagDefinitions?.[tag] ?? 5) < 5 ? "bg-red-500" : "bg-gray-400")} />
@@ -247,21 +237,92 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
</div>
</div>
</div>
<div class="h-px w-full bg-border mt-4" />
</div>
</div>
{/* Editor Content */}
<div class="flex-1 overflow-y-auto px-6 py-4 pb-20">
<Suspense fallback={<div class="h-32 animate-pulse bg-muted/20 rounded-lg" />}>
<TaskEditor
content={props.task.content}
onUpdate={updateTaskContent}
onEditorReady={setEditorInstance}
/>
</Suspense>
{/* Sticky Footer Actions */}
<div class="px-6 py-4 border-t border-border/50 flex items-center justify-between shrink-0 bg-background/80 backdrop-blur-sm">
{/* Delete button (persistent) */}
<Button
variant="ghost"
size="sm"
class="h-9 px-3 gap-2 hover:bg-destructive/10 hover:text-destructive text-muted-foreground transition-all rounded-xl border border-border/40"
onClick={() => {
const taskId = props.task.id;
removeTask(taskId);
props.onClose();
toast.info("Task moved to trash", {
action: {
label: "Undo",
onClick: () => restoreTask(taskId)
}
});
}}
>
<Trash2 size={16} />
<span class="text-xs font-bold uppercase tracking-wider">Delete</span>
</Button>
{/* Close button (Mobile only) */}
<Button
variant="ghost"
size="sm"
class="md:hidden h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40"
onClick={() => props.onClose()}
>
<X size={16} />
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
</Button>
</div>
</SheetContent>
</Sheet >
</Sheet>
);
};
// Reusable Metadata Item with slide transition
const MetadataItem: Component<{
label: string,
date: string,
time: string,
icon?: any,
editable?: boolean,
urgency?: string,
children?: any,
onClick?: () => void
}> = (props) => {
const [isToggled, setIsToggled] = createSignal(false);
return (
<div
class="group relative flex flex-col gap-0.5 cursor-pointer select-none"
onMouseEnter={() => setIsToggled(true)}
onMouseLeave={() => setIsToggled(false)}
onClick={() => {
setIsToggled(!isToggled());
props.onClick?.();
}}
>
<span class="text-[9px] font-bold uppercase tracking-wider text-muted-foreground/60 transition-colors group-hover:text-primary/70">
{props.label}
</span>
<div class="h-4 overflow-hidden relative min-w-[100px]">
<div class={cn(
"flex flex-col transition-transform duration-500 cubic-bezier(0.4, 0, 0.2, 1)",
isToggled() ? "-translate-y-4" : "translate-y-0"
)}>
{/* Layer 1: Date */}
<div class="h-4 flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground/80 whitespace-nowrap">
{props.icon}
{props.date}
</div>
{/* Layer 2: Time */}
<div class="h-4 flex items-center gap-2 text-[11px] font-bold text-foreground whitespace-nowrap">
<Clock size={11} class="text-primary/60 shrink-0" />
<span>{props.time}</span>
</div>
</div>
</div>
{props.children}
</div>
);
};
+4 -2
View File
@@ -20,8 +20,8 @@ const SheetOverlay: Component<ComponentProps<typeof SheetPrimitive.Overlay>> = (
);
};
const SheetContent: Component<ComponentProps<typeof SheetPrimitive.Content>> = (props) => {
const [local, others] = splitProps(props, ["class", "children"]);
const SheetContent: Component<ComponentProps<typeof SheetPrimitive.Content> & { hideCloseButton?: boolean }> = (props) => {
const [local, others] = splitProps(props, ["class", "children", "hideCloseButton"]);
return (
<SheetPrimitive.Portal>
<SheetOverlay />
@@ -33,10 +33,12 @@ const SheetContent: Component<ComponentProps<typeof SheetPrimitive.Content>> = (
{...others}
>
{/* Close Button */}
{!local.hideCloseButton && (
<SheetPrimitive.CloseButton class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</SheetPrimitive.CloseButton>
)}
{local.children}
</SheetPrimitive.Content>
</SheetPrimitive.Portal>
+15 -4
View File
@@ -23,6 +23,8 @@ export interface Task {
tags: string[];
content?: string; // HTML content from Tiptap
deletedAt?: number; // Timestamp of soft delete
created: string; // ISO string from PB
updated: string; // ISO string from PB
}
export interface Filter {
@@ -212,7 +214,9 @@ export const initStore = async () => {
completed: r.completed,
tags: r.tags || [],
content: r.content,
deletedAt: r.deletedAt ? new Date(r.deletedAt).getTime() : undefined
deletedAt: r.deletedAt ? new Date(r.deletedAt).getTime() : undefined,
created: r.created,
updated: r.updated
}));
// Use reconcile for efficient store update (keeps observers happy)
@@ -254,7 +258,9 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
priority,
completed: false,
tags,
content
content,
created: new Date().toISOString(),
updated: new Date().toISOString()
};
// Optimistic UI update
@@ -272,8 +278,13 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
content
});
// Replace temp task with real record
setStore("tasks", (t) => t.map(task => task.id === tempId ? { ...task, id: record.id } : task));
// Replace temp task with real record (merging server fields like id, created, updated)
setStore("tasks", (t) => t.map(task => task.id === tempId ? {
...task,
id: record.id,
created: record.created,
updated: record.updated
} : task));
} catch (err) {
console.error("create failed", err);
toast.error("Failed to save task.");
+45 -37
View File
@@ -12,37 +12,48 @@ import { createSignal } from "solid-js";
export const SettingsView: Component = () => {
return (
<div class="py-10 max-w-2xl mx-auto space-y-10 animate-in fade-in slide-in-from-bottom-2 duration-500">
<header class="space-y-1 flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold tracking-tight">Settings</h1>
<p class="text-muted-foreground">{pb.authStore.model?.email}</p>
<div class="w-full max-w-2xl mx-auto py-4 sm:py-10 px-4 sm:px-0 space-y-8 sm:space-y-12 animate-in fade-in slide-in-from-bottom-2 duration-500 min-w-0 overflow-hidden text-balance">
<header class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div class="min-w-0 flex-1">
<h1 class="text-2xl sm:text-4xl font-black tracking-tighter">Settings</h1>
<p class="text-[10px] sm:text-xs text-muted-foreground truncate opacity-70 font-mono tracking-tight" title={pb.authStore.model?.email}>
{pb.authStore.model?.email}
</p>
</div>
<Button variant="ghost" size="sm" onClick={() => { pb.authStore.clear(); location.reload(); }}>Sign Out</Button>
<Button
variant="outline"
size="sm"
class="w-full sm:w-auto font-black uppercase tracking-widest text-[10px] h-10 sm:h-8 rounded-xl shadow-sm border-border/60"
onClick={() => { pb.authStore.clear(); location.reload(); }}
>
Sign Out
</Button>
</header>
<div class="grid gap-6">
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm">
<div class="flex items-center justify-between">
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div class="space-y-0.5">
<h3 class="text-base font-semibold">Appearance</h3>
<p class="text-sm text-muted-foreground">Select your preferred color scheme.</p>
</div>
<div class="flex justify-start sm:justify-end">
<ThemeToggle />
</div>
</div>
</section>
{/* Matrix Configuration */}
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<div class="flex items-center justify-between">
<div class="space-y-0.5">
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div class="space-y-0.5 min-w-0">
<h3 class="text-base font-semibold flex items-center gap-2">
<ArrowLeftRight size={16} />
Matrix Horizon
</h3>
<p class="text-sm text-muted-foreground">Adjust the time scale for your strategy grid.</p>
<p class="text-sm text-muted-foreground truncate sm:whitespace-normal">Adjust the time scale for your strategy grid.</p>
</div>
<div class="w-[120px]">
<div class="w-full sm:w-[120px] shrink-0">
<Select
value={store.matrixScaleDays.toString()}
onChange={(val) => val && setMatrixScaleDays(parseInt(val))}
@@ -50,7 +61,7 @@ export const SettingsView: Component = () => {
placeholder="Select days"
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue} days</SelectItem>}
>
<SelectTrigger class="h-9">
<SelectTrigger class="h-9 w-full">
<span class="text-sm font-medium">{store.matrixScaleDays} days</span>
</SelectTrigger>
<SelectContent />
@@ -59,7 +70,7 @@ export const SettingsView: Component = () => {
</div>
</section>
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<div class="space-y-0.5">
<h3 class="text-base font-semibold flex items-center gap-2">
<Hash size={16} />
@@ -79,20 +90,20 @@ export const SettingsView: Component = () => {
const [tempName, setTempName] = createSignal(tagName);
const handleRename = () => {
if (tempName() !== tagName) {
if (tempName() && tempName() !== tagName) {
renameTagDefinition(tagName, tempName());
}
setIsEditingName(false);
};
return (
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/30 border border-border/50">
<div class="flex items-center gap-3 flex-1">
<div class="flex flex-col sm:flex-row sm:items-center justify-between p-3 rounded-xl bg-muted/30 border border-border/50 gap-3 sm:gap-4">
<div class="flex items-center gap-3 min-w-0 flex-1">
<div class={cn("w-2 h-2 rounded-full shrink-0", val > 5 ? "bg-green-500" : val < 5 ? "bg-red-500" : "bg-gray-400")} />
{isEditingName() ? (
<input
class="flex h-7 w-48 rounded-md border border-input bg-background px-2 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
class="flex h-7 min-w-0 w-full rounded-md border border-input bg-background px-2 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
value={tempName()}
onInput={(e) => setTempName(e.currentTarget.value)}
onBlur={handleRename}
@@ -101,7 +112,7 @@ export const SettingsView: Component = () => {
/>
) : (
<span
class="font-medium cursor-pointer hover:underline decoration-dashed underline-offset-4"
class="font-medium cursor-pointer hover:underline decoration-dashed underline-offset-4 truncate"
onClick={() => setIsEditingName(true)}
title="Click to rename"
>
@@ -110,7 +121,7 @@ export const SettingsView: Component = () => {
)}
</div>
<div class="flex items-center gap-4">
<div class="flex items-center grow sm:grow-0 justify-between sm:justify-end gap-3 sm:gap-4 shrink-0 border-t sm:border-t-0 pt-2 sm:pt-0 border-border/20">
<div class="flex items-center gap-2">
<span class="text-[10px] text-muted-foreground font-bold uppercase tracking-wider">Val</span>
<select
@@ -144,13 +155,13 @@ export const SettingsView: Component = () => {
</div>
</section>
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<div class="space-y-0.5">
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4 min-w-0 overflow-hidden">
<div class="space-y-0.5 min-w-0">
<h3 class="text-base font-semibold flex items-center gap-2">
<Trash2 size={16} />
Trash
</h3>
<p class="text-sm text-muted-foreground">Items are permanently deleted after 7 days.</p>
<p class="text-sm text-muted-foreground truncate">Items are permanently deleted after 7 days.</p>
</div>
<div class="space-y-2">
@@ -162,35 +173,32 @@ export const SettingsView: Component = () => {
{(task) => {
const daysLeft = Math.ceil(((task.deletedAt || 0) + (7 * 24 * 60 * 60 * 1000) - Date.now()) / (1000 * 60 * 60 * 24));
return (
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/30 border border-border/50 group hover:bg-muted/50 transition-colors">
<div class="min-w-0 flex-1 mr-4">
<p class="font-medium truncate">{task.title || "Untitled"}</p>
<p class="text-[10px] text-muted-foreground">Expires in {Math.max(0, daysLeft)} days</p>
<div class="flex flex-col sm:flex-row sm:items-center justify-between p-3.5 rounded-2xl bg-muted/20 border border-border/40 group hover:bg-muted/40 transition-all gap-3 min-w-0">
<div class="min-w-0 flex-1 px-1">
<p class="font-semibold truncate text-sm sm:text-base">{task.title || "Untitled"}</p>
<p class="text-[11px] text-muted-foreground opacity-70">Expires in {Math.max(0, daysLeft)} days</p>
</div>
<div class="flex items-center gap-1">
<div class="flex items-center justify-end gap-2 shrink-0 border-t sm:border-t-0 pt-3 sm:pt-0 border-border/10">
<Button
variant="ghost"
variant="secondary"
size="sm"
class="h-8 px-2 text-xs hover:bg-green-500/10 hover:text-green-600"
class="h-9 px-4 text-[10px] font-bold uppercase tracking-widest hover:bg-green-500/10 hover:text-green-600 bg-background/50 border border-border/50 transition-all rounded-xl shadow-sm"
onClick={() => {
restoreTask(task.id);
toast.success("Task restored");
}}
>
<Undo2 size={14} class="mr-1" />
<Undo2 size={14} class="mr-2" />
Recover
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8 hover:bg-red-500/10 hover:text-red-600"
class="h-9 w-9 hover:bg-red-500/10 hover:text-red-600 rounded-xl border border-border/50 bg-background/30"
onClick={() => {
// For permanent deletion, still using confirm for safety but ensuring toast follows
if (confirm("Permanently delete this task? This cannot be undone.")) {
deleteTaskPermanently(task.id);
toast.error("Task permanently deleted", {
duration: 3000
});
toast.error("Task permanently deleted");
}
}}
>