initial commit 3??
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import { type Component, createEffect, createSignal } from "solid-js";
|
||||
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
||||
import { type Task, removeTask, restoreTask, updateTask } from "@/store";
|
||||
import { TaskEditor } from "@/components/TaskEditor";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
import { ArrowUpCircle, Clock, Calendar, Type, Trash2 } from "lucide-solid";
|
||||
import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
|
||||
import { Button } from "./ui/button";
|
||||
import { toast } from "solid-sonner";
|
||||
|
||||
interface TaskDetailProps {
|
||||
task: Task;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
// Local state for immediate feedback, synced with props
|
||||
const [title, setTitle] = createSignal(props.task.title);
|
||||
const [editorInstance, setEditorInstance] = createSignal<any>(null);
|
||||
|
||||
// Sync input string for datetime-local
|
||||
const [dateString, setDateString] = createSignal<string>("");
|
||||
|
||||
createEffect(() => {
|
||||
setTitle(props.task.title);
|
||||
// Format for datetime-local: yyyy-MM-ddThh:mm (local time)
|
||||
const dateObj = new Date(props.task.dueDate);
|
||||
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
|
||||
setDateString(localIso);
|
||||
});
|
||||
|
||||
let debounceTimer: number | undefined;
|
||||
const updateTaskContent = (html: string) => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
updateTask(props.task.id, { content: html });
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
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 });
|
||||
};
|
||||
|
||||
const updatePriority = (val: string) => {
|
||||
const p = parseInt(val);
|
||||
if (!isNaN(p)) {
|
||||
updateTask(props.task.id, { priority: p });
|
||||
}
|
||||
};
|
||||
|
||||
const updateUrgency = (val: string) => {
|
||||
const u = parseInt(val);
|
||||
if (!isNaN(u)) {
|
||||
const newDate = calculateDateFromUrgency(u);
|
||||
updateTask(props.task.id, { dueDate: newDate });
|
||||
}
|
||||
};
|
||||
|
||||
const onDateInputChange = (e: Event) => {
|
||||
const val = (e.currentTarget as HTMLInputElement).value;
|
||||
if (val) {
|
||||
const iso = new Date(val).toISOString();
|
||||
updateTask(props.task.id, { dueDate: iso });
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate current urgency level for display
|
||||
const currentUrgency = () => calculateUrgencyFromDate(props.task.dueDate).toString();
|
||||
|
||||
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">
|
||||
{/* Header Area */}
|
||||
<div class="px-6 pt-6 pb-2 shrink-0">
|
||||
<textarea
|
||||
value={title()}
|
||||
onInput={(e) => {
|
||||
updateTitle(e.currentTarget.value);
|
||||
// Auto-resize
|
||||
e.currentTarget.style.height = 'auto';
|
||||
e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
|
||||
}}
|
||||
ref={(el) => {
|
||||
setTimeout(() => {
|
||||
el.style.height = 'auto';
|
||||
el.style.height = el.scrollHeight + 'px';
|
||||
}, 0);
|
||||
}}
|
||||
rows={1}
|
||||
class="text-xl sm:text-2xl font-bold bg-transparent border-none focus:outline-none focus:ring-0 w-full placeholder:text-muted-foreground/50 resize-none overflow-hidden leading-tight"
|
||||
placeholder="Untitled Task"
|
||||
/>
|
||||
|
||||
{/* Properties Bar */}
|
||||
<div class="flex flex-row items-center gap-1.5 sm:gap-4 mt-2 text-sm overflow-x-auto no-scrollbar flex-nowrap">
|
||||
{/* 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>
|
||||
<Select
|
||||
value={props.task.priority.toString()}
|
||||
onChange={(val) => val && updatePriority(val)}
|
||||
options={["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]}
|
||||
placeholder="Priority"
|
||||
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
||||
>
|
||||
<SelectTrigger class="h-8 min-w-[40px] bg-transparent border-transparent hover:bg-muted/50 px-1 sm:px-2 shadow-none focus:ring-0 shrink-0">
|
||||
<div class="flex items-center gap-1 sm:gap-1.5 text-foreground font-medium">
|
||||
<ArrowUpCircle size={14} class="text-primary opacity-70 group-hover:opacity-100 transition-opacity" />
|
||||
<span class="text-xs sm:text-sm">{props.task.priority}</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Urgency */}
|
||||
<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">Time</span>
|
||||
<Select
|
||||
value={currentUrgency()}
|
||||
onChange={(val) => val && updateUrgency(val)}
|
||||
options={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
placeholder="Urgency"
|
||||
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
||||
>
|
||||
<SelectTrigger class="h-8 min-w-[40px] bg-transparent border-transparent hover:bg-muted/50 px-1 sm:px-2 shadow-none focus:ring-0 shrink-0">
|
||||
<div class="flex items-center gap-1 sm:gap-1.5 text-foreground font-medium">
|
||||
<Clock size={14} class="text-primary opacity-70 group-hover:opacity-100 transition-opacity" />
|
||||
<span class="text-xs sm:text-sm">{currentUrgency()}</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Commands Shortcut */}
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8 px-1 sm:px-2 text-[10px] font-bold uppercase tracking-wider hover:bg-muted/50 flex items-center gap-1 sm:gap-1.5 text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => {
|
||||
const instance = editorInstance();
|
||||
if (instance) {
|
||||
instance.chain().focus().insertContent("/").run();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Type size={14} class="opacity-70" />
|
||||
<span class="hidden sm:inline">Commands</span>
|
||||
</Button>
|
||||
</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" />
|
||||
<input
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Delete 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-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", {
|
||||
action: {
|
||||
label: "Undo",
|
||||
onClick: () => restoreTask(taskId)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-px w-full bg-border mt-4" />
|
||||
</div>
|
||||
|
||||
{/* Editor Content */}
|
||||
<div class="flex-1 overflow-y-auto px-6 py-4 pb-20">
|
||||
<TaskEditor
|
||||
content={props.task.content}
|
||||
onUpdate={updateTaskContent}
|
||||
onEditorReady={setEditorInstance}
|
||||
/>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user