fixed urgency-date connection and added urgency-date guidance. fixed password extension break. fixed template saving.
This commit is contained in:
@@ -39,7 +39,6 @@ export const AuthCallback = (props: { onSuccess: () => void }) => {
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
placeholder="Email"
|
||||
value={email()}
|
||||
onInput={(e) => setEmail(e.currentTarget.value)}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
required
|
||||
@@ -52,7 +51,6 @@ export const AuthCallback = (props: { onSuccess: () => void }) => {
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
placeholder="Password"
|
||||
value={password()}
|
||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
required
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TagPicker } from "@/components/TagPicker";
|
||||
import { lazy, Suspense } from "solid-js";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { toast } from "solid-sonner";
|
||||
import { PRIORITY_OPTIONS, URGENCY_OPTIONS } from "@/lib/constants";
|
||||
|
||||
const TaskEditor = lazy(() => import("./TaskEditor").then(m => ({ default: m.TaskEditor })));
|
||||
|
||||
@@ -19,6 +20,7 @@ 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");
|
||||
const [tags, setTags] = createSignal<string[]>([]);
|
||||
@@ -104,9 +106,12 @@ export const QuickEntry: Component = () => {
|
||||
onCleanup(() => window.removeEventListener("keydown", handleKeyDown));
|
||||
|
||||
// When urgency changes via dropdown, update the date
|
||||
const onUrgencySimpleChange = (val: string) => {
|
||||
setUrgency(val);
|
||||
const level = parseInt(val);
|
||||
const onUrgencySimpleChange = (val: any) => {
|
||||
const value = typeof val === 'object' ? val.value : val;
|
||||
if (!value) return;
|
||||
|
||||
setUrgency(value);
|
||||
const level = parseInt(value);
|
||||
if (!isNaN(level)) {
|
||||
const newDateIso = calculateDateFromUrgency(level);
|
||||
// Format for datetime-local input: YYYY-MM-DDTHH:mm
|
||||
@@ -118,7 +123,7 @@ export const QuickEntry: Component = () => {
|
||||
|
||||
// When date changes via input, update urgency level
|
||||
const onDateInputChange = (e: Event) => {
|
||||
const val = (e.target as HTMLInputElement).value;
|
||||
const val = (e.currentTarget as HTMLInputElement).value;
|
||||
setDateString(val);
|
||||
if (val) {
|
||||
const level = calculateUrgencyFromDate(new Date(val).toISOString());
|
||||
@@ -278,21 +283,28 @@ export const QuickEntry: Component = () => {
|
||||
<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
|
||||
<Select<any>
|
||||
value={priority()}
|
||||
onChange={(val) => val && setPriority(val)}
|
||||
options={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
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}
|
||||
{props.item.rawValue.label}
|
||||
</SelectItem>
|
||||
)}
|
||||
>
|
||||
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<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">{priority()}</span>
|
||||
<span class="text-sm font-medium truncate">
|
||||
{PRIORITY_OPTIONS.find(o => o.value === priority())?.label || priority()}
|
||||
</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
@@ -301,23 +313,25 @@ export const QuickEntry: Component = () => {
|
||||
|
||||
<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
|
||||
<Select<any>
|
||||
value={urgency()}
|
||||
onChange={(val) => {
|
||||
if (val) onUrgencySimpleChange(val);
|
||||
}}
|
||||
options={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
onChange={(val) => onUrgencySimpleChange(val)}
|
||||
options={URGENCY_OPTIONS}
|
||||
optionValue="value"
|
||||
optionTextValue="label"
|
||||
placeholder="Urgency"
|
||||
itemComponent={(props) => (
|
||||
<SelectItem item={props.item}>
|
||||
{props.item.rawValue}
|
||||
{props.item.rawValue.label}
|
||||
</SelectItem>
|
||||
)}
|
||||
>
|
||||
<SelectTrigger class="w-full bg-background border-none shadow-sm h-10 px-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<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">{urgency()}</span>
|
||||
<span class="text-sm font-medium truncate">
|
||||
{URGENCY_OPTIONS.find(o => o.value === urgency())?.label || urgency()}
|
||||
</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
|
||||
@@ -73,7 +73,7 @@ export const TagPicker: Component<TagPickerProps> = (props) => {
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">Value (Influence)</label>
|
||||
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">Value (Sorting Influence)</label>
|
||||
<select
|
||||
value={valueScore().toString()}
|
||||
onInput={(e) => setValueScore(parseInt(e.currentTarget.value))}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
|
||||
import { Button } from "./ui/button";
|
||||
import { TagPicker } from "./TagPicker";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { PRIORITY_OPTIONS, URGENCY_OPTIONS } from "@/lib/constants";
|
||||
import { store } from "@/store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "solid-sonner";
|
||||
@@ -49,15 +50,17 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
updateTask(props.task.id, { title: val });
|
||||
};
|
||||
|
||||
const updatePriority = (val: string) => {
|
||||
const p = parseInt(val);
|
||||
const updatePriority = (val: any) => {
|
||||
const value = typeof val === 'object' ? val.value : val;
|
||||
const p = parseInt(value);
|
||||
if (!isNaN(p)) {
|
||||
updateTask(props.task.id, { priority: p });
|
||||
}
|
||||
};
|
||||
|
||||
const updateUrgency = (val: string) => {
|
||||
const u = parseInt(val);
|
||||
const updateUrgency = (val: any) => {
|
||||
const value = typeof val === 'object' ? val.value : val;
|
||||
const u = parseInt(value);
|
||||
if (!isNaN(u)) {
|
||||
const newDate = calculateDateFromUrgency(u);
|
||||
updateTask(props.task.id, { dueDate: newDate });
|
||||
@@ -119,17 +122,21 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
{/* 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
|
||||
<Select<any>
|
||||
value={props.task.priority.toString()}
|
||||
onChange={(val: string | null) => val && updatePriority(val)}
|
||||
options={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
options={PRIORITY_OPTIONS}
|
||||
optionValue="value"
|
||||
optionTextValue="label"
|
||||
placeholder="Priority"
|
||||
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
||||
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue.label}</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>
|
||||
<SelectTrigger class="h-8 min-w-[40px] max-w-[180px] bg-transparent border-transparent hover:bg-muted/50 px-1 sm:px-2 shadow-none focus:ring-0 shrink-0 overflow-hidden">
|
||||
<div class="flex items-center gap-1 sm:gap-1.5 text-foreground font-medium truncate">
|
||||
<ArrowUpCircle size={14} class="text-primary opacity-70 group-hover:opacity-100 transition-opacity shrink-0" />
|
||||
<span class="text-xs sm:text-sm truncate">
|
||||
{PRIORITY_OPTIONS.find(o => o.value === props.task.priority.toString())?.label || props.task.priority}
|
||||
</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
@@ -139,17 +146,21 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
{/* 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
|
||||
<Select<any>
|
||||
value={currentUrgency()}
|
||||
onChange={(val: string | null) => val && updateUrgency(val)}
|
||||
options={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}
|
||||
options={URGENCY_OPTIONS}
|
||||
optionValue="value"
|
||||
optionTextValue="label"
|
||||
placeholder="Urgency"
|
||||
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
||||
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue.label}</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>
|
||||
<SelectTrigger class="h-8 min-w-[40px] max-w-[180px] bg-transparent border-transparent hover:bg-muted/50 px-1 sm:px-2 shadow-none focus:ring-0 shrink-0 overflow-hidden">
|
||||
<div class="flex items-center gap-1 sm:gap-1.5 text-foreground font-medium truncate">
|
||||
<Clock size={14} class="text-primary opacity-70 group-hover:opacity-100 transition-opacity shrink-0" />
|
||||
<span class="text-xs sm:text-sm truncate">
|
||||
{URGENCY_OPTIONS.find(o => o.value === currentUrgency())?.label || currentUrgency()}
|
||||
</span>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
export const PRIORITY_OPTIONS = [
|
||||
{ value: "10", label: "10 - Necessary" },
|
||||
{ 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 - Trivial" }
|
||||
];
|
||||
|
||||
export const URGENCY_OPTIONS = [
|
||||
{ value: "10", label: "10 - Today" },
|
||||
{ value: "9", label: "9 - Tomorrow Morning" },
|
||||
{ value: "8", label: "8 - EOD Tomorrow" },
|
||||
{ value: "7", label: "7 - Half-Week" },
|
||||
{ value: "6", label: "6 - This Week/5 Days" },
|
||||
{ value: "5", label: "5 - Next Week/10 Days" },
|
||||
{ value: "4", label: "4 - 3 Weeks" },
|
||||
{ value: "3", label: "3 - 6 Weeks" },
|
||||
{ value: "2", label: "2 - 3 Months" },
|
||||
{ value: "1", label: "1 - 6 Months" }
|
||||
];
|
||||
export const URGENCY_HOURS: Record<number, number> = {
|
||||
10: 12,
|
||||
9: 24,
|
||||
8: 36,
|
||||
7: 72,
|
||||
6: 120,
|
||||
5: 240,
|
||||
4: 504,
|
||||
3: 1080,
|
||||
2: 2160,
|
||||
1: 4320
|
||||
};
|
||||
+28
-92
@@ -2,6 +2,7 @@ import { createStore, reconcile, produce } from "solid-js/store";
|
||||
import { createSignal, createEffect, createRoot } from "solid-js";
|
||||
import { pb, TASGRID_COLLECTION } from "@/lib/pocketbase";
|
||||
import { toast } from "solid-sonner";
|
||||
import { URGENCY_HOURS } from "@/lib/constants";
|
||||
|
||||
const getStorageKey = () => {
|
||||
const userId = pb.authStore.model?.id;
|
||||
@@ -117,25 +118,31 @@ export const calculateUrgencyScore = (dueDateStr: string): number => {
|
||||
const due = new Date(dueDateStr).getTime();
|
||||
const diffHours = (due - now()) / (1000 * 60 * 60);
|
||||
|
||||
// Geometric formulation
|
||||
// Base is derived so that urgency 1 ~ matrixScaleDays
|
||||
const base = Math.pow(Math.max(1, store.matrixScaleDays), 1 / 9);
|
||||
if (diffHours <= URGENCY_HOURS[10]) return 10;
|
||||
if (diffHours >= URGENCY_HOURS[1]) return 1;
|
||||
|
||||
// Reverse formula: Level = 10 - (ln(hours/24) / ln(base))
|
||||
// If hours <= 24, log(<=1) is negative or 0, so Level >= 10.
|
||||
if (diffHours <= 24) return 10;
|
||||
// Linear interpolation between breakpoints
|
||||
const levels = Object.keys(URGENCY_HOURS).map(Number).sort((a, b) => b - a); // 10, 9, 8...
|
||||
for (let i = 0; i < levels.length - 1; i++) {
|
||||
const highLevel = levels[i];
|
||||
const lowLevel = levels[i + 1];
|
||||
const highHours = URGENCY_HOURS[highLevel];
|
||||
const lowHours = URGENCY_HOURS[lowLevel];
|
||||
|
||||
const level = 10 - (Math.log(diffHours / 24) / Math.log(base));
|
||||
return Math.max(1, Math.min(10, level));
|
||||
if (diffHours >= highHours && diffHours <= lowHours) {
|
||||
// Level decreases as hours increase
|
||||
const range = lowHours - highHours;
|
||||
const progress = (diffHours - highHours) / range;
|
||||
return highLevel - progress * (highLevel - lowLevel);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
export const calculateDateFromUrgency = (level: number): string => {
|
||||
// Inverse of above
|
||||
if (level >= 10) {
|
||||
return new Date(now() + 24 * 60 * 60 * 1000).toISOString();
|
||||
}
|
||||
const base = Math.pow(Math.max(1, store.matrixScaleDays), 1 / 9);
|
||||
const hours = 24 * Math.pow(base, 10 - level);
|
||||
const roundedLevel = Math.max(1, Math.min(10, Math.round(level)));
|
||||
const hours = URGENCY_HOURS[roundedLevel] || 24;
|
||||
return new Date(now() + hours * 60 * 60 * 1000).toISOString();
|
||||
};
|
||||
|
||||
@@ -400,24 +407,7 @@ export const updateTaskField = (id: string, field: keyof Task, value: any) => {
|
||||
|
||||
export const setMatrixScaleDays = async (days: number) => {
|
||||
setStore("matrixScaleDays", days);
|
||||
|
||||
// Sync to users collection
|
||||
if (store.prefId) {
|
||||
try {
|
||||
const prefs = {
|
||||
pWeight: store.pWeight,
|
||||
uWeight: store.uWeight,
|
||||
matrixScaleDays: days
|
||||
};
|
||||
|
||||
await pb.collection('users').update(store.prefId, {
|
||||
Taskgrid_pref: prefs
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error("Failed to update preferences", err);
|
||||
}
|
||||
}
|
||||
await syncPreferences();
|
||||
};
|
||||
|
||||
export const toggleTask = (id: string) => {
|
||||
@@ -491,27 +481,7 @@ export const upsertMultipleTagDefinitions = async (updates: Record<string, numbe
|
||||
setStore("tagDefinitions", (defs) => ({ ...(defs || {}), ...updates }));
|
||||
|
||||
// Persist
|
||||
const map = { ...store.tagDefinitions };
|
||||
const userId = pb.authStore.model?.id;
|
||||
if (userId) {
|
||||
try {
|
||||
const prefs = {
|
||||
pWeight: store.pWeight,
|
||||
uWeight: store.uWeight,
|
||||
matrixScaleDays: store.matrixScaleDays,
|
||||
tagDefinitions: map
|
||||
};
|
||||
|
||||
await pb.collection('users').update(userId, {
|
||||
Taskgrid_pref: prefs
|
||||
}, { requestKey: null });
|
||||
} catch (e: any) {
|
||||
// Ignore abort errors
|
||||
if (e.isAbort) return;
|
||||
console.error("Failed to persist tag definitions", e);
|
||||
toast.error("Failed to save tag definitions");
|
||||
}
|
||||
}
|
||||
await syncPreferences();
|
||||
};
|
||||
|
||||
export const removeTagDefinition = async (name: string) => {
|
||||
@@ -528,25 +498,8 @@ export const removeTagDefinition = async (name: string) => {
|
||||
}));
|
||||
|
||||
// 3. Persist
|
||||
const map = { ...store.tagDefinitions };
|
||||
const userId = pb.authStore.model?.id;
|
||||
if (userId) {
|
||||
try {
|
||||
const prefs = {
|
||||
pWeight: store.pWeight,
|
||||
uWeight: store.uWeight,
|
||||
matrixScaleDays: store.matrixScaleDays,
|
||||
tagDefinitions: map
|
||||
};
|
||||
await pb.collection('users').update(userId, {
|
||||
Taskgrid_pref: prefs
|
||||
});
|
||||
toast.success(`Tag "${name}" deleted`);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete tag definition", e);
|
||||
toast.error("Failed to delete tag");
|
||||
}
|
||||
}
|
||||
await syncPreferences();
|
||||
toast.success(`Tag "${name}" deleted`);
|
||||
};
|
||||
|
||||
export const renameTagDefinition = async (oldName: string, newName: string) => {
|
||||
@@ -574,31 +527,13 @@ export const renameTagDefinition = async (oldName: string, newName: string) => {
|
||||
}));
|
||||
|
||||
// 4. Persist
|
||||
const map = { ...store.tagDefinitions };
|
||||
const userId = pb.authStore.model?.id;
|
||||
if (userId) {
|
||||
try {
|
||||
const prefs = {
|
||||
pWeight: store.pWeight,
|
||||
uWeight: store.uWeight,
|
||||
matrixScaleDays: store.matrixScaleDays,
|
||||
tagDefinitions: map
|
||||
};
|
||||
await pb.collection('users').update(userId, {
|
||||
Taskgrid_pref: prefs
|
||||
});
|
||||
toast.success(`Tag renamed to "${finalName}"`);
|
||||
} catch (e) {
|
||||
console.error("Failed to rename tag", e);
|
||||
toast.error("Failed to rename tag");
|
||||
}
|
||||
}
|
||||
await syncPreferences();
|
||||
};
|
||||
|
||||
|
||||
// -- Templates --
|
||||
|
||||
const syncPreferences = async () => {
|
||||
export const syncPreferences = async () => {
|
||||
const userId = pb.authStore.model?.id;
|
||||
if (!userId) return;
|
||||
|
||||
@@ -617,6 +552,7 @@ const syncPreferences = async () => {
|
||||
} catch (e: any) {
|
||||
if (e.isAbort) return;
|
||||
console.error("Failed to sync preferences", e);
|
||||
// We could toast here, but many of these are background syncs
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "solid-sonner";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
import { PRIORITY_OPTIONS, URGENCY_OPTIONS } from "@/lib/constants";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
const TaskEditor = lazy(() => import("../components/TaskEditor").then(m => ({ default: m.TaskEditor })));
|
||||
@@ -221,6 +222,8 @@ export const SettingsView: Component = () => {
|
||||
{(template) => {
|
||||
const [isEditingName, setIsEditingName] = createSignal(false);
|
||||
const [editName, setEditName] = createSignal(template.name);
|
||||
const [editTitle, setEditTitle] = createSignal(template.title);
|
||||
|
||||
const isExpanded = () => expandedTemplateId() === template.id;
|
||||
|
||||
const handleSaveName = () => {
|
||||
@@ -230,6 +233,12 @@ export const SettingsView: Component = () => {
|
||||
setIsEditingName(false);
|
||||
};
|
||||
|
||||
const handleSaveTitle = () => {
|
||||
if (editTitle() !== template.title) {
|
||||
updateTemplate(template.id, { title: editTitle() });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={cn(
|
||||
"flex flex-col rounded-2xl border transition-all overflow-hidden shadow-sm",
|
||||
@@ -269,7 +278,7 @@ export const SettingsView: Component = () => {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 hover:bg-primary/10 hover:text-primary rounded-xl"
|
||||
onClick={(e) => {
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setIsEditingName(true);
|
||||
}}
|
||||
@@ -280,7 +289,7 @@ export const SettingsView: Component = () => {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 hover:bg-red-500/10 hover:text-red-600 rounded-xl"
|
||||
onClick={(e) => {
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (confirm(`Delete template "${template.name}"?`)) {
|
||||
removeTemplate(template.id);
|
||||
@@ -300,8 +309,9 @@ export const SettingsView: Component = () => {
|
||||
<label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground ml-1">Title Placeholder</label>
|
||||
<input
|
||||
class="flex h-9 w-full rounded-lg border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
value={template.title}
|
||||
onInput={(e) => updateTemplate(template.id, { title: e.currentTarget.value })}
|
||||
value={editTitle()}
|
||||
onInput={(e) => setEditTitle(e.currentTarget.value)}
|
||||
onBlur={handleSaveTitle}
|
||||
placeholder="Default task title..."
|
||||
/>
|
||||
</div>
|
||||
@@ -313,8 +323,8 @@ export const SettingsView: Component = () => {
|
||||
value={template.priority}
|
||||
onChange={(e) => updateTemplate(template.id, { priority: parseInt(e.currentTarget.value) })}
|
||||
>
|
||||
<For each={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}>
|
||||
{(v) => <option value={v}>{v}</option>}
|
||||
<For each={PRIORITY_OPTIONS}>
|
||||
{(opt) => <option value={opt.value}>{opt.label}</option>}
|
||||
</For>
|
||||
</select>
|
||||
</div>
|
||||
@@ -325,8 +335,8 @@ export const SettingsView: Component = () => {
|
||||
value={template.urgency}
|
||||
onChange={(e) => updateTemplate(template.id, { urgency: parseInt(e.currentTarget.value) })}
|
||||
>
|
||||
<For each={["10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]}>
|
||||
{(v) => <option value={v}>{v}</option>}
|
||||
<For each={URGENCY_OPTIONS}>
|
||||
{(opt) => <option value={opt.value}>{opt.label}</option>}
|
||||
</For>
|
||||
</select>
|
||||
</div>
|
||||
@@ -360,7 +370,13 @@ export const SettingsView: Component = () => {
|
||||
<Suspense fallback={<div class="h-20 animate-pulse bg-muted/20 rounded-lg" />}>
|
||||
<TaskEditor
|
||||
content={template.content}
|
||||
onUpdate={(html) => updateTemplate(template.id, { content: html })}
|
||||
onUpdate={(html) => {
|
||||
// For the rich editor, we can use a slightly debounced store update
|
||||
// to avoid losing focus if the parent re-renders,
|
||||
// although Tiptap usually handles its own state well.
|
||||
// But to be safe, let's just update the store.
|
||||
updateTemplate(template.id, { content: html });
|
||||
}}
|
||||
class="min-h-[100px]"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
Reference in New Issue
Block a user