filter and layout improvements. added created at and modified at date/time stamps
This commit is contained in:
@@ -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">
|
||||
<FilterBar />
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+141
-80
@@ -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,93 +164,46 @@ 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"
|
||||
/>
|
||||
</div>
|
||||
</MetadataItem>
|
||||
|
||||
{/* 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>
|
||||
<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' })}
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
<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>
|
||||
|
||||
{/* 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">
|
||||
<TagPicker
|
||||
selectedTags={props.task.tags || []}
|
||||
onTagsChange={updateTags}
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<For each={props.task.tags || []}>
|
||||
{(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"
|
||||
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")} />
|
||||
{tag}
|
||||
</Badge>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-px w-full bg-border mt-4" />
|
||||
<div class="h-px w-full bg-border mt-5" />
|
||||
</div>
|
||||
|
||||
{/* Editor Content */}
|
||||
<div class="flex-1 overflow-y-auto px-6 py-4 pb-20">
|
||||
{/* 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}
|
||||
@@ -260,8 +211,118 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
onEditorReady={setEditorInstance}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* 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}
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<For each={props.task.tags || []}>
|
||||
{(tag) => (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
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")} />
|
||||
{tag}
|
||||
</Badge>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 */}
|
||||
<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.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
@@ -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.");
|
||||
|
||||
+46
-38
@@ -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>
|
||||
<ThemeToggle />
|
||||
<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");
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user