initial commit 3??
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { type Component, For, createMemo } from "solid-js";
|
||||
import { store, getCombinedScore } from "@/store";
|
||||
import { TaskCard } from "@/components/TaskCard";
|
||||
|
||||
export const CriticalView: Component = () => {
|
||||
const sortedTasks = createMemo(() => {
|
||||
return [...store.tasks].filter(t => !t.deletedAt).sort((a, b) => {
|
||||
if (a.completed !== b.completed) return a.completed ? 1 : -1;
|
||||
return getCombinedScore(b) - getCombinedScore(a);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-3xl font-bold tracking-tight">Focus</h2>
|
||||
<p class="text-muted-foreground mt-1 text-lg">Your tasks ranked by highest urgency + priority.</p>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<For each={sortedTasks()}>
|
||||
{(task) => <TaskCard task={task} />}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
{sortedTasks().length === 0 && (
|
||||
<div class="flex flex-col items-center justify-center py-20 text-center">
|
||||
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4">
|
||||
<span class="text-2xl">✨</span>
|
||||
</div>
|
||||
<p class="text-muted-foreground">Clear horizon. No critical tasks pending.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import { type Component, For, createMemo } from "solid-js";
|
||||
import { store, calculateUrgencyScore, setActiveTaskId, setStore, getCombinedScore } from "@/store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
|
||||
export const MatrixView: Component = () => {
|
||||
const activeTasks = createMemo(() => store.tasks.filter(t => !t.completed && !t.deletedAt));
|
||||
|
||||
const scaleOptions = ["1", "7", "30", "60", "90"];
|
||||
|
||||
return (
|
||||
<div class="h-[calc(100vh-12rem)] md:h-[calc(100vh-8rem)] flex flex-col">
|
||||
<header class="mb-6">
|
||||
<h2 class="text-3xl font-bold tracking-tight">Strategy Matrix</h2>
|
||||
<p class="text-muted-foreground mt-1 text-lg">Urgency vs Priority mapping.</p>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 relative border-2 border-dashed border-muted rounded-3xl overflow-hidden bg-muted/20">
|
||||
{/* Axis Labels */}
|
||||
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 text-[10px] font-bold uppercase tracking-widest text-muted-foreground/50 flex items-center gap-2 z-30">
|
||||
<span>← Soon</span>
|
||||
<div class="flex items-center gap-1 bg-background/50 backdrop-blur-sm px-2 py-0.5 rounded-full border border-border/50 group hover:bg-background/80 transition-colors cursor-pointer">
|
||||
<span class="text-[9px]">Time:</span>
|
||||
<Select
|
||||
value={store.matrixScaleDays.toString()}
|
||||
onChange={(val) => val && setStore("matrixScaleDays", parseInt(val))}
|
||||
options={scaleOptions}
|
||||
placeholder="Scale"
|
||||
itemComponent={(props) => (
|
||||
<SelectItem item={props.item}>
|
||||
{props.item.rawValue} {props.item.rawValue === "1" ? "day" : "days"}
|
||||
</SelectItem>
|
||||
)}
|
||||
>
|
||||
<SelectTrigger class="h-auto p-0 min-w-[50px] bg-transparent border-none shadow-none focus:ring-0 text-[10px] font-black text-foreground">
|
||||
<span>{store.matrixScaleDays} {store.matrixScaleDays === 1 ? 'day' : 'days'}</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent class="z-[150]" />
|
||||
</Select>
|
||||
</div>
|
||||
<span>Later →</span>
|
||||
</div>
|
||||
<div class="absolute left-4 top-1/2 -translate-y-1/2 -rotate-90 text-[10px] font-bold uppercase tracking-widest text-muted-foreground/50 pointer-events-none">
|
||||
← Low (Priority) High →
|
||||
</div>
|
||||
|
||||
{/* Quadrant Lines */}
|
||||
<div class="absolute inset-0 flex pointer-events-none">
|
||||
<div class="flex-1 border-r border-muted/30" />
|
||||
<div class="flex-1" />
|
||||
</div>
|
||||
<div class="absolute inset-0 flex flex-col pointer-events-none">
|
||||
<div class="flex-1 border-b border-muted/30" />
|
||||
<div class="flex-1" />
|
||||
</div>
|
||||
|
||||
{/* Quadrant Labels */}
|
||||
<div class="absolute top-4 left-4 text-[10px] font-bold text-muted-foreground/30 pointer-events-none">DO FIRST</div>
|
||||
<div class="absolute top-4 right-4 text-[10px] font-bold text-muted-foreground/30 pointer-events-none">SCHEDULE</div>
|
||||
<div class="absolute bottom-4 left-4 text-[10px] font-bold text-muted-foreground/30 pointer-events-none">DELEGATE</div>
|
||||
<div class="absolute bottom-4 right-4 text-[10px] font-bold text-muted-foreground/30 pointer-events-none">ELIMINATE</div>
|
||||
|
||||
{/* Task Pips */}
|
||||
<For each={activeTasks()}>
|
||||
{(task) => {
|
||||
const getPosition = () => {
|
||||
// Continuous geometric urgency mapping
|
||||
const now = new Date().getTime();
|
||||
const due = new Date(task.dueDate).getTime();
|
||||
const diffHours = Math.max(0.1, (due - now) / (1000 * 60 * 60));
|
||||
|
||||
// Level formula: H = 24 * base^(10-v) => v = 10 - logBase(H/24)
|
||||
const base = Math.pow(Math.max(1, store.matrixScaleDays), 1 / 9);
|
||||
const continuousLevel = 10 - (Math.log(diffHours / 24) / Math.log(base));
|
||||
const clampedLevel = Math.min(10, Math.max(1, continuousLevel));
|
||||
|
||||
// Map 1-10 level to 10-90% X coordinate (10 on left, 1 on right)
|
||||
const x = 100 - (((clampedLevel - 1) / 9) * 80 + 10);
|
||||
|
||||
// Y-axis: Priority (1 to 10)
|
||||
// 10 = 10%, 1 = 90%
|
||||
const y = 100 - (((task.priority - 1) / 9) * 80 + 10);
|
||||
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
const urgencyScore = calculateUrgencyScore(task.dueDate);
|
||||
const combinedScore = getCombinedScore(task);
|
||||
|
||||
// Map 1-10 to HSL Hue (0 = Red, 240 = Blue)
|
||||
// Using a slightly limited range for better aesthetics (0 to 220)
|
||||
const hue = ((10 - combinedScore) / 9) * 220;
|
||||
const dotColor = `hsl(${hue}, 85%, 55%)`;
|
||||
|
||||
return (
|
||||
<div
|
||||
class="absolute group cursor-pointer z-10"
|
||||
onClick={() => setActiveTaskId(task.id)}
|
||||
style={{
|
||||
left: `${getPosition().x}%`,
|
||||
top: `${getPosition().y}%`,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="w-4 h-4 rounded-full border-2 border-background shadow-lg transition-all duration-300 group-hover:scale-150 group-hover:ring-4"
|
||||
style={{
|
||||
"background-color": dotColor,
|
||||
"--tw-ring-color": `${dotColor}40` // 25% opacity for ring
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Tooltip */}
|
||||
<div class={cn(
|
||||
"absolute left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-all pointer-events-none z-20 w-48",
|
||||
getPosition().y < 25 ? "top-full mt-3" : "bottom-full mb-3"
|
||||
)}>
|
||||
<div class={cn(
|
||||
"w-2 h-2 bg-popover border-l border-t border-border rotate-45 mx-auto",
|
||||
getPosition().y < 25 ? "-mb-1 order-first" : "-mt-1 order-last"
|
||||
)} />
|
||||
<div class="bg-popover text-popover-foreground border border-border rounded-xl p-3 shadow-2xl scale-95 group-hover:scale-100 transition-transform">
|
||||
<p class="text-xs font-bold leading-tight">{task.title}</p>
|
||||
<div class="flex items-center justify-between mt-2 pt-2 border-t border-border">
|
||||
<span class="text-[9px] text-muted-foreground">U: {urgencyScore}</span>
|
||||
<span class="text-[9px] text-muted-foreground">P: {task.priority}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class={cn(
|
||||
"w-2 h-2 bg-popover border-r border-b border-border rotate-45 mx-auto",
|
||||
getPosition().y < 25 ? "hidden" : "-mt-1"
|
||||
)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { type Component, For, createMemo } from "solid-js";
|
||||
import { store, calculateUrgencyScore } from "@/store";
|
||||
import { TaskCard } from "@/components/TaskCard";
|
||||
|
||||
export const PriorityView: Component = () => {
|
||||
const sortedTasks = createMemo(() => {
|
||||
return [...store.tasks].filter(t => !t.deletedAt).sort((a, b) => {
|
||||
if (a.completed !== b.completed) return a.completed ? 1 : -1;
|
||||
if (a.priority !== b.priority) return b.priority - a.priority;
|
||||
const uA = calculateUrgencyScore(a.dueDate);
|
||||
const uB = calculateUrgencyScore(b.dueDate);
|
||||
return uB - uA;
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-3xl font-bold tracking-tight">Priority</h2>
|
||||
<p class="text-muted-foreground mt-1 text-lg">Your tasks ordered by priority.</p>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<For each={sortedTasks()}>
|
||||
{(task) => <TaskCard task={task} />}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
{sortedTasks().length === 0 && (
|
||||
<div class="flex flex-col items-center justify-center py-20 text-center">
|
||||
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4">
|
||||
<span class="text-2xl">🎯</span>
|
||||
</div>
|
||||
<p class="text-muted-foreground">Focus sharp. No high-priority tasks yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
import { type Component, For } from "solid-js";
|
||||
import { ThemeToggle } from "../components/ThemeToggle";
|
||||
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays } from "@/store";
|
||||
import { Trash2, Undo2, ArrowLeftRight } from "lucide-solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "solid-sonner";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
export const SettingsView: Component = () => {
|
||||
return (
|
||||
<div class="px-6 py-10 max-w-2xl mx-auto space-y-10 animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<header class="space-y-1 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Settings</h1>
|
||||
<p class="text-muted-foreground">{pb.authStore.model?.email}</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => { pb.authStore.clear(); location.reload(); }}>Sign Out</Button>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6">
|
||||
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<h3 class="text-base font-semibold">Appearance</h3>
|
||||
<p class="text-sm text-muted-foreground">Select your preferred color scheme.</p>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Matrix Configuration */}
|
||||
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<h3 class="text-base font-semibold flex items-center gap-2">
|
||||
<ArrowLeftRight size={16} />
|
||||
Matrix Horizon
|
||||
</h3>
|
||||
<p class="text-sm text-muted-foreground">Adjust the time scale for your strategy grid.</p>
|
||||
</div>
|
||||
<div class="w-[120px]">
|
||||
<Select
|
||||
value={store.matrixScaleDays.toString()}
|
||||
onChange={(val) => val && setMatrixScaleDays(parseInt(val))}
|
||||
options={["7", "14", "30", "60", "90"]}
|
||||
placeholder="Select days"
|
||||
itemComponent={(props) => <SelectItem item={props.item}>{props.item.rawValue} days</SelectItem>}
|
||||
>
|
||||
<SelectTrigger class="h-9">
|
||||
<span class="text-sm font-medium">{store.matrixScaleDays} days</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent />
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
|
||||
<div class="space-y-0.5">
|
||||
<h3 class="text-base font-semibold flex items-center gap-2">
|
||||
<Trash2 size={16} />
|
||||
Trash
|
||||
</h3>
|
||||
<p class="text-sm text-muted-foreground">Items are permanently deleted after 7 days.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<For each={store.tasks.filter(t => t.deletedAt)} fallback={
|
||||
<div class="text-center py-8 text-muted-foreground text-sm italic border border-dashed border-border rounded-xl">
|
||||
Trash is empty.
|
||||
</div>
|
||||
}>
|
||||
{(task) => {
|
||||
const daysLeft = Math.ceil(((task.deletedAt || 0) + (7 * 24 * 60 * 60 * 1000) - Date.now()) / (1000 * 60 * 60 * 24));
|
||||
return (
|
||||
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/30 border border-border/50 group hover:bg-muted/50 transition-colors">
|
||||
<div class="min-w-0 flex-1 mr-4">
|
||||
<p class="font-medium truncate">{task.title || "Untitled"}</p>
|
||||
<p class="text-[10px] text-muted-foreground">Expires in {Math.max(0, daysLeft)} days</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8 px-2 text-xs hover:bg-green-500/10 hover:text-green-600"
|
||||
onClick={() => {
|
||||
restoreTask(task.id);
|
||||
toast.success("Task restored");
|
||||
}}
|
||||
>
|
||||
<Undo2 size={14} class="mr-1" />
|
||||
Recover
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 hover:bg-red-500/10 hover:text-red-600"
|
||||
onClick={() => {
|
||||
// For permanent deletion, still using confirm for safety but ensuring toast follows
|
||||
if (confirm("Permanently delete this task? This cannot be undone.")) {
|
||||
deleteTaskPermanently(task.id);
|
||||
toast.error("Task permanently deleted", {
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { type Component, For, createMemo } from "solid-js";
|
||||
import { store, calculateUrgencyScore } from "@/store";
|
||||
import { TaskCard } from "@/components/TaskCard";
|
||||
|
||||
export const UrgencyView: Component = () => {
|
||||
const sortedTasks = createMemo(() => {
|
||||
return [...store.tasks].filter(t => !t.deletedAt).sort((a, b) => {
|
||||
if (a.completed !== b.completed) return a.completed ? 1 : -1;
|
||||
const uA = calculateUrgencyScore(a.dueDate);
|
||||
const uB = calculateUrgencyScore(b.dueDate);
|
||||
if (uA !== uB) return uB - uA;
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-3xl font-bold tracking-tight">Time</h2>
|
||||
<p class="text-muted-foreground mt-1 text-lg">Your tasks ordered by deadline.</p>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<For each={sortedTasks()}>
|
||||
{(task) => <TaskCard task={task} />}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
{sortedTasks().length === 0 && (
|
||||
<div class="flex flex-col items-center justify-center py-20 text-center">
|
||||
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4">
|
||||
<span class="text-2xl">⏳</span>
|
||||
</div>
|
||||
<p class="text-muted-foreground">All clear. No time-sensitive tasks.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user