initial commit 3??

This commit is contained in:
2026-01-30 14:10:11 -06:00
parent 34bbe8e98f
commit f2a75c954f
42 changed files with 3935 additions and 1 deletions
+251
View File
@@ -0,0 +1,251 @@
import { type Component, createSignal, createEffect, onCleanup, onMount } from "solid-js";
import { Search, Command, Clock, ArrowUpCircle, Plus } from "lucide-solid";
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
import { Button } from "@/components/ui/button";
import { TextField, TextFieldInput } from "@/components/ui/textfield";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
export const QuickEntry: Component = () => {
const [isOpen, setIsOpen] = createSignal(false);
const [input, setInput] = createSignal("");
const [priority, setPriority] = createSignal<string>("5");
const [urgency, setUrgency] = createSignal<string>("5");
// Reactive shorthand parsing
createEffect(() => {
const text = input();
const pMatch = text.match(/\/p(\d+)/);
if (pMatch) {
const val = Math.min(10, Math.max(1, parseInt(pMatch[1]))).toString();
setPriority(val);
}
const uMatch = text.match(/\/u(\d+)/);
if (uMatch) {
const val = Math.min(10, Math.max(1, parseInt(uMatch[1]))).toString();
if (val !== urgency()) {
onUrgencySimpleChange(val);
}
}
});
// Explicit date string for the input (YYYY-MM-DDTHH:mm)
const [dateString, setDateString] = createSignal<string>("");
let inputRef: HTMLInputElement | undefined;
createEffect(() => {
if (isOpen()) {
// Slight delay to ensure DOM is ready and animations don't interfere
setTimeout(() => inputRef?.focus(), 50);
}
});
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
if (isOpen()) {
inputRef?.focus();
} else {
setIsOpen(true);
}
}
if (e.key === "Escape") {
setIsOpen(false);
}
};
onMount(() => window.addEventListener("keydown", handleKeyDown));
onCleanup(() => window.removeEventListener("keydown", handleKeyDown));
// When urgency changes via dropdown, update the date
const onUrgencySimpleChange = (val: string) => {
setUrgency(val);
const level = parseInt(val);
if (!isNaN(level)) {
const newDateIso = calculateDateFromUrgency(level);
// Format for datetime-local input: YYYY-MM-DDTHH:mm
const dateObj = new Date(newDateIso);
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
setDateString(localIso);
}
};
// When date changes via input, update urgency level
const onDateInputChange = (e: Event) => {
const val = (e.target as HTMLInputElement).value;
setDateString(val);
if (val) {
const level = calculateUrgencyFromDate(new Date(val).toISOString());
setUrgency(level.toString());
}
};
const parseAndAdd = () => {
const text = input();
if (!text || !priority() || !urgency()) return;
let finalPriority = parseInt(priority());
let finalUrgency = parseInt(urgency());
// Parse shorthand overrides if present
const pMatch = text.match(/\/p(\d+)/);
if (pMatch) finalPriority = Math.min(10, Math.max(1, parseInt(pMatch[1])));
// Check for urgency override
const uMatchNumeric = text.match(/\/u(\d+)/);
if (uMatchNumeric) {
finalUrgency = Math.min(10, Math.max(1, parseInt(uMatchNumeric[1])));
}
let finalDueDate: Date | null = null;
if (dateString()) {
finalDueDate = new Date(dateString());
} else {
// If urgency is selected, calculate date
const u = parseInt(urgency());
if (!isNaN(u)) {
// Check if override happened
if (uMatchNumeric) {
const dateStr = calculateDateFromUrgency(finalUrgency);
finalDueDate = new Date(dateStr);
} else {
const dateStr = calculateDateFromUrgency(u);
finalDueDate = new Date(dateStr);
}
}
}
const title = text
.replace(/\/p\d+/, "")
.replace(/\/u\d+/, "")
.trim();
// Use the new signature: addTask(title, options)
addTask(title, {
priority: finalPriority,
dueDate: finalDueDate || undefined, // undefined will let store pick default if that was the intent, but here we probably want strictly calculated date
tags: []
});
setInput("");
setPriority("5");
setUrgency("5");
setDateString("");
setIsOpen(false);
};
return (
<>
<Button
size="icon"
onClick={() => setIsOpen(true)}
class="fixed bottom-20 left-[80%] -translate-x-1/2 md:bottom-10 md:right-10 md:left-auto md:translate-x-0 w-14 h-14 rounded-full shadow-2xl z-50 group hover:scale-110 transition-all duration-300"
>
<Plus size={28} class="group-hover:rotate-90 transition-transform duration-300" />
</Button>
{isOpen() && (
<div class="fixed inset-0 z-[100] flex items-start justify-center pt-[15vh] px-4">
<div class="absolute inset-0 bg-background/80 backdrop-blur-sm" onClick={() => setIsOpen(false)} />
<div class="relative w-full max-w-xl bg-card border border-border rounded-2xl shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200">
<div class="flex items-center px-4 py-3 border-b border-border">
<Search class="text-muted-foreground mr-3" size={20} />
<TextField class="flex-1">
<TextFieldInput
ref={inputRef}
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
onKeyDown={(e) => e.key === "Enter" && parseAndAdd()}
placeholder="Task title... type /p1-10 for priority and /u1-10 for urgency"
class="border-none shadow-none focus-visible:ring-0 text-lg"
/>
</TextField>
</div>
<div class="flex flex-col md:flex-row items-stretch md:items-center p-4 gap-4 border-b border-border bg-muted/10">
<div class="flex-1 space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Priority</label>
<Select
value={priority()}
onChange={(val) => val && setPriority(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="w-full bg-background border-none shadow-sm h-10 px-3">
<div class="flex items-center gap-2">
<ArrowUpCircle size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium">{priority()}</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="flex-1 space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Urgency (Time)</label>
<Select
value={urgency()}
onChange={(val) => {
if (val) onUrgencySimpleChange(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="w-full bg-background border-none shadow-sm h-10 px-3">
<div class="flex items-center gap-2">
<Clock size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium">{urgency()}</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="flex-1 space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Due Date</label>
<div class="relative">
<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="w-full h-10 px-3 bg-background border-none rounded-md text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring z-[102] relative cursor-pointer"
/>
</div>
</div>
</div>
<div class="p-4 bg-muted/30 flex justify-between items-center">
<div class="flex items-center space-x-2 text-[10px] font-bold text-muted-foreground uppercase tracking-widest">
<Command size={10} />
<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>
</div>
</div>
)}
</>
);
};