Compare commits

...

2 Commits

2 changed files with 186 additions and 175 deletions
+175 -164
View File
@@ -1,6 +1,6 @@
import { type Component, createSignal, createEffect, onCleanup, onMount, For, Show } from "solid-js";
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertTagDefinition, type TaskTemplate } from "@/store";
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown, Gauge } from "lucide-solid";
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown, Gauge, X } from "lucide-solid";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { TextField, TextFieldInput } from "@/components/ui/textfield";
@@ -258,11 +258,11 @@ export const QuickEntry: Component = () => {
</Button>
{isOpen() && (
<div class="fixed inset-0 z-[100] flex items-start justify-center pt-[15vh] px-4">
<div class="fixed inset-0 z-[100] flex items-end md:items-start justify-center md:pt-[15vh] px-0 md:px-4 pb-0 md:pb-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">
<div class="relative w-full max-w-xl max-h-[100vh] md:max-h-[80vh] bg-card border border-border rounded-t-2xl md:rounded-2xl shadow-2xl overflow-hidden animate-in fade-in slide-in-from-bottom-4 md:zoom-in-95 duration-200 flex flex-col">
<div class="flex items-center px-4 py-3 border-b border-border shrink-0">
<Search class="text-muted-foreground mr-3" size={20} />
<TextField class="flex-1">
<TextFieldInput
@@ -276,173 +276,184 @@ export const QuickEntry: Component = () => {
class="border-none shadow-none focus-visible:ring-0 text-lg"
/>
</TextField>
<Button
size="icon"
variant="ghost"
onClick={() => setIsOpen(false)}
class="ml-2 md:hidden h-9 w-9 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted"
>
<X size={20} />
</Button>
</div>
{/* Row 1: Priority, Size, Urgency */}
<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 <span class="text-destructive">*</span>
</label>
<Select<any>
value={priority()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setPriority(v);
}}
options={PRIORITY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Priority"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<ArrowUpCircle size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{PRIORITY_OPTIONS.find(o => o.value === priority())?.label || (priority() === "" ? "Pick Priority" : 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">Size</label>
<Select<any>
value={size()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setSize(v);
}}
options={SIZE_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Size"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<Gauge size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{SIZE_OPTIONS.find(o => o.value === size())?.label || size()}
</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 <span class="text-destructive">*</span>
</label>
<Select<any>
value={urgency()}
onChange={(val) => onUrgencySimpleChange(val)}
options={URGENCY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Urgency"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<Clock size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{URGENCY_OPTIONS.find(o => o.value === urgency())?.label || (urgency() === "" ? "Pick Urgency" : urgency())}
</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
</div>
{/* Row 2: Tags + Due Date */}
<div class="flex flex-col md:flex-row items-stretch md:items-center px-4 py-3 gap-4 bg-muted/10 border-b border-border">
<div class="flex-1 flex flex-wrap gap-2 items-center">
<TagPicker selectedTags={tags()} onTagsChange={setTags} />
<For each={tags()}>
{(tag) => (
<Badge
variant="secondary"
class="h-7 px-2 text-xs gap-1 bg-background border border-border/50 cursor-pointer hover:bg-destructive/10 hover:border-destructive/30 group/tag transition-all"
style={{
"background-color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
const color = getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme());
return color ? `${color}15` : undefined;
})(),
"border-color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
const color = getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme());
return color ? `${color}30` : undefined;
})(),
"color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
return getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme()) || "inherit";
})()
}}
onClick={() => setTags(tags().filter(t => t !== tag))}
>
<div
class="w-1.5 h-1.5 rounded-full transition-colors group-hover/tag:bg-destructive"
style={{
"background-color": (store.tagDefinitions.find(d => d.name === tag)?.value ?? 5) > 5 ? "#22c55e" : (store.tagDefinitions.find(d => d.name === tag)?.value ?? 5) < 5 ? "#ef4444" : "#94a3b8"
}}
/>
{tag}
</Badge>
)}
</For>
</div>
<div class="w-full md:w-auto md:min-w-[200px] 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();
}
{/* Scrollable content area */}
<div class="flex-1 overflow-y-auto overscroll-contain">
{/* Row 1: Priority, Size, Urgency */}
<div class="flex flex-col md:flex-row flex-wrap items-stretch md:items-center p-4 gap-4 border-b border-border bg-muted/10">
<div class="flex-1 min-w-0 md:min-w-[140px] space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">
Priority <span class="text-destructive">*</span>
</label>
<Select<any>
value={priority()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setPriority(v);
}}
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"
/>
options={PRIORITY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Priority"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<ArrowUpCircle size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{PRIORITY_OPTIONS.find(o => o.value === priority())?.label || (priority() === "" ? "Pick Priority" : priority())}
</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="flex-1 min-w-0 md:min-w-[140px] space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Size</label>
<Select<any>
value={size()}
onChange={(val) => {
const v = typeof val === 'object' ? val.value : val;
if (v) setSize(v);
}}
options={SIZE_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Size"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<Gauge size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{SIZE_OPTIONS.find(o => o.value === size())?.label || size()}
</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
<div class="flex-1 min-w-0 md:min-w-[140px] space-y-1.5">
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">
Urgency <span class="text-destructive">*</span>
</label>
<Select<any>
value={urgency()}
onChange={(val) => onUrgencySimpleChange(val)}
options={URGENCY_OPTIONS}
optionValue="value"
optionTextValue="label"
placeholder="Urgency"
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.label}
</SelectItem>
)}
>
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3 overflow-hidden">
<div class="flex items-center gap-2 truncate">
<Clock size={14} class="text-primary shrink-0" />
<span class="text-sm font-medium truncate">
{URGENCY_OPTIONS.find(o => o.value === urgency())?.label || (urgency() === "" ? "Pick Urgency" : urgency())}
</span>
</div>
</SelectTrigger>
<SelectContent />
</Select>
</div>
</div>
{/* Row 2: Tags + Due Date */}
<div class="flex flex-col md:flex-row items-stretch md:items-center px-4 py-3 gap-4 bg-muted/10 border-b border-border">
<div class="flex-1 flex flex-wrap gap-2 items-center">
<TagPicker selectedTags={tags()} onTagsChange={setTags} />
<For each={tags()}>
{(tag) => (
<Badge
variant="secondary"
class="h-7 px-2 text-xs gap-1 bg-background border border-border/50 cursor-pointer hover:bg-destructive/10 hover:border-destructive/30 group/tag transition-all"
style={{
"background-color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
const color = getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme());
return color ? `${color}15` : undefined;
})(),
"border-color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
const color = getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme());
return color ? `${color}30` : undefined;
})(),
"color": (() => {
const def = store.tagDefinitions.find(d => d.name === tag);
return getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme()) || "inherit";
})()
}}
onClick={() => setTags(tags().filter(t => t !== tag))}
>
<div
class="w-1.5 h-1.5 rounded-full transition-colors group-hover/tag:bg-destructive"
style={{
"background-color": (store.tagDefinitions.find(d => d.name === tag)?.value ?? 5) > 5 ? "#22c55e" : (store.tagDefinitions.find(d => d.name === tag)?.value ?? 5) < 5 ? "#ef4444" : "#94a3b8"
}}
/>
{tag}
</Badge>
)}
</For>
</div>
<div class="w-full md:w-auto md:min-w-[200px] 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="px-4 py-3 bg-card min-h-[80px]">
<Suspense fallback={<div class="h-20 animate-pulse bg-muted/20 rounded-lg" />}>
<TaskEditor
content={description()}
onUpdate={setDescription}
onEditorReady={setEditorInstance}
class="min-h-[60px] prose-sm"
/>
</Suspense>
</div>
</div>
<div class="px-4 py-3 bg-card border-b border-border min-h-[120px] max-h-[300px] overflow-y-auto">
<Suspense fallback={<div class="h-20 animate-pulse bg-muted/20 rounded-lg" />}>
<TaskEditor
content={description()}
onUpdate={setDescription}
onEditorReady={setEditorInstance}
class="min-h-[100px] prose-sm"
/>
</Suspense>
</div>
<div class="p-4 bg-muted/30 flex justify-between items-center">
<div class="p-4 bg-muted/30 flex justify-between items-center shrink-0 border-t border-border">
<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>
+11 -11
View File
@@ -37,15 +37,15 @@ export const URGENCY_HOURS: Record<number, number> = {
};
export const SIZE_OPTIONS = [
{ value: "10", label: "10 - Huge" },
{ value: "9", label: "9" },
{ value: "8", label: "8" },
{ value: "7", label: "7" },
{ value: "6", label: "6" },
{ value: "5", label: "5" },
{ value: "4", label: "4" },
{ value: "3", label: "3" },
{ value: "2", label: "2" },
{ value: "1", label: "1" },
{ value: "0", label: "0 - Tiny" }
{ value: "10", label: "10 - Multiple Days" },
{ value: "9", label: "9 - More than a day" },
{ value: "8", label: "8 - One day" },
{ value: "7", label: "7 - Half a day" },
{ value: "6", label: "6 - Two hours" },
{ value: "5", label: "5 - One hour" },
{ value: "4", label: "4 - Forty-Five minutes" },
{ value: "3", label: "3 - Thirty minutes" },
{ value: "2", label: "2 - Fifteen minutes" },
{ value: "1", label: "1 - Five minutes" },
{ value: "0", label: "0 - Less than a minute" }
];