added templates and fixed UX for virtual keyboard opening when opening a task
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { type Component, createSignal, createEffect, onCleanup, onMount, For } from "solid-js";
|
||||
import { Search, Command, Clock, ArrowUpCircle, Plus } from "lucide-solid";
|
||||
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertMultipleTagDefinitions } from "@/store";
|
||||
import { type Component, createSignal, createEffect, onCleanup, onMount, For, Show } from "solid-js";
|
||||
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertMultipleTagDefinitions, type TaskTemplate } from "@/store";
|
||||
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown } from "lucide-solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -8,6 +8,8 @@ import { TextField, TextFieldInput } from "@/components/ui/textfield";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
import { TagPicker } from "@/components/TagPicker";
|
||||
import { lazy, Suspense } from "solid-js";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { toast } from "solid-sonner";
|
||||
|
||||
const TaskEditor = lazy(() => import("./TaskEditor").then(m => ({ default: m.TaskEditor })));
|
||||
|
||||
@@ -219,6 +221,29 @@ export const QuickEntry: Component = () => {
|
||||
if (instance) instance.commands.setContent("");
|
||||
};
|
||||
|
||||
const applyTemplate = (template: TaskTemplate) => {
|
||||
setInput(template.title);
|
||||
setPriority(template.priority.toString());
|
||||
setUrgency(template.urgency.toString());
|
||||
setTags([...(template.tags || [])]);
|
||||
setDescription(template.content || "");
|
||||
|
||||
// Sync urgency to date
|
||||
const level = template.urgency;
|
||||
const newDateIso = calculateDateFromUrgency(level);
|
||||
const dateObj = new Date(newDateIso);
|
||||
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
|
||||
setDateString(localIso);
|
||||
|
||||
// Update editor if ready
|
||||
const instance = editorInstance();
|
||||
if (instance) {
|
||||
instance.commands.setContent(template.content || "");
|
||||
}
|
||||
|
||||
toast.info(`Applied template: ${template.name}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
@@ -352,9 +377,49 @@ export const QuickEntry: Component = () => {
|
||||
<span class="hidden sm:inline">K to focus • Enter to Add</span>
|
||||
<span class="sm:hidden">Enter to Add</span>
|
||||
</div>
|
||||
<Button size="sm" onClick={parseAndAdd} disabled={!input() || !priority() || !urgency()}>
|
||||
Create Task
|
||||
</Button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Show when={(store.templates || []).length > 0}>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
as={Button}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 px-3 gap-2 hover:bg-primary/10 hover:text-primary text-muted-foreground transition-all rounded-xl border border-border/40"
|
||||
>
|
||||
<Copy size={14} />
|
||||
<span class="text-[10px] font-bold uppercase tracking-wider hidden sm:inline">Use Template</span>
|
||||
<ChevronDown size={12} class="opacity-50" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-64 p-2 bg-card border-border shadow-xl">
|
||||
<div class="space-y-1">
|
||||
<For each={store.templates}>
|
||||
{(template) => (
|
||||
<button
|
||||
class="w-full flex items-center gap-3 p-2 rounded-lg hover:bg-muted transition-colors text-left group"
|
||||
onClick={() => applyTemplate(template)}
|
||||
>
|
||||
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0 group-hover:bg-primary group-hover:text-primary-foreground transition-all">
|
||||
<Copy size={14} />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-bold truncate tracking-tight">{template.name}</p>
|
||||
<p class="text-[9px] uppercase font-black text-muted-foreground opacity-60 tracking-widest">
|
||||
P:{template.priority} / U:{template.urgency}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Show>
|
||||
|
||||
<Button size="sm" onClick={parseAndAdd} disabled={!input() || !priority() || !urgency()}>
|
||||
Create Task
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 } from "@/store";
|
||||
import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate } from "@/store";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X } from "lucide-solid";
|
||||
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy } from "lucide-solid";
|
||||
import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
|
||||
import { Button } from "./ui/button";
|
||||
import { TagPicker } from "./TagPicker";
|
||||
@@ -81,7 +81,16 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
|
||||
return (
|
||||
<Sheet open={props.isOpen} onOpenChange={(open) => !open && props.onClose()}>
|
||||
<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">
|
||||
<SheetContent
|
||||
hideCloseButton
|
||||
onOpenAutoFocus={(e) => {
|
||||
// Prevent auto-focus on mobile to avoid keyboard popup
|
||||
if (window.innerWidth < 768) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
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
|
||||
@@ -263,16 +272,35 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
<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 class="flex items-center gap-2">
|
||||
{/* Save as Template (Desktop + Mobile) */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-9 px-3 gap-2 hover:bg-primary/10 hover:text-primary text-muted-foreground transition-all rounded-xl border border-border/40"
|
||||
onClick={async () => {
|
||||
const name = prompt("Enter a name for this template:");
|
||||
if (name) {
|
||||
await saveTaskAsTemplate(props.task.id, name);
|
||||
toast.success(`Template "${name}" created`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Copy size={16} />
|
||||
<span class="text-xs font-bold uppercase tracking-wider">Template</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>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
Reference in New Issue
Block a user