added task transitions to movement

This commit is contained in:
2026-03-03 18:54:40 -06:00
parent 15f518c58f
commit f1200a2c78
11 changed files with 276 additions and 95 deletions
+25 -12
View File
@@ -1,15 +1,24 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const CriticalView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.sort((a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
return getCombinedScore(b) - getCombinedScore(a);
});
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -19,13 +28,17 @@ export const CriticalView: Component = () => {
<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} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>
+30 -17
View File
@@ -1,20 +1,29 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const DigInView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.sort((a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// Primary: Size descending (largest first)
const sizeA = a.size ?? 5;
const sizeB = b.size ?? 5;
if (sizeA !== sizeB) return sizeB - sizeA;
// Secondary: Combined score descending (higher focus first)
return getCombinedScore(b) - getCombinedScore(a);
});
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// Primary: Size descending (largest first)
const sizeA = a.size ?? 5;
const sizeB = b.size ?? 5;
if (sizeA !== sizeB) return sizeB - sizeA;
// Secondary: Combined score descending (higher focus first)
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -24,13 +33,17 @@ export const DigInView: Component = () => {
<p class="text-muted-foreground mt-1 text-lg">Largest tasks first. Tackle big projects head-on.</p>
</header>
<div class="grid gap-3">
<For each={sortedTasks()}>
{(task) => <TaskCard task={task} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>
+25 -13
View File
@@ -1,16 +1,24 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const PriorityView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.sort((a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// Use combined score to account for priority, urgency, and tags
return getCombinedScore(b) - getCombinedScore(a);
});
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -20,13 +28,17 @@ export const PriorityView: Component = () => {
<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} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>
+30 -17
View File
@@ -1,22 +1,31 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const ProgressView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.sort((a, b) => {
// 1. Completed tasks at the bottom
if (a.completed !== b.completed) return a.completed ? 1 : -1;
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
// 2. Sort by Progress (Status) Descending (Highest progress first)
// Assuming status 0-9 are active states, 10 is completed (handled above)
if (a.status !== b.status) return b.status - a.status;
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(a, b) => {
// 1. Completed tasks at the bottom
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// 3. Then by Focus Score (Combined Score)
return getCombinedScore(b) - getCombinedScore(a);
});
// 2. Sort by Progress (Status) Descending (Highest progress first)
// Assuming status 0-9 are active states, 10 is completed (handled above)
if (a.status !== b.status) return b.status - a.status;
// 3. Then by Focus Score (Combined Score)
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -26,13 +35,17 @@ export const ProgressView: Component = () => {
<p class="text-muted-foreground mt-1 text-lg">Tasks sorted by progress status, then by focus score.</p>
</header>
<div class="grid gap-3">
<For each={sortedTasks()}>
{(task) => <TaskCard task={task} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>
+30 -17
View File
@@ -1,20 +1,29 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const SnowballView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.sort((a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// Primary: Size ascending (smallest first)
const sizeA = a.size ?? 5;
const sizeB = b.size ?? 5;
if (sizeA !== sizeB) return sizeA - sizeB;
// Secondary: Combined score descending (higher focus first)
return getCombinedScore(b) - getCombinedScore(a);
});
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
// Primary: Size ascending (smallest first)
const sizeA = a.size ?? 5;
const sizeB = b.size ?? 5;
if (sizeA !== sizeB) return sizeA - sizeB;
// Secondary: Combined score descending (higher focus first)
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -24,13 +33,17 @@ export const SnowballView: Component = () => {
<p class="text-muted-foreground mt-1 text-lg">Smallest tasks first. Build momentum with quick wins.</p>
</header>
<div class="grid gap-3">
<For each={sortedTasks()}>
{(task) => <TaskCard task={task} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>
+29 -16
View File
@@ -1,19 +1,28 @@
import { type Component, For, createMemo } from "solid-js";
import { type Component, For, createMemo, onMount } from "solid-js";
import { store, calculateUrgencyScore, getCombinedScore, matchesFilter } from "@/store";
import { TaskCard } from "@/components/TaskCard";
import { useDelayedSort } from "@/hooks/useDelayedSort";
import autoAnimate from "@formkit/auto-animate";
export const UrgencyView: Component = () => {
const sortedTasks = createMemo(() => {
return [...store.tasks]
.filter(t => !t.deletedAt && matchesFilter(t))
.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;
// Tie-break with combined score (Priority + Tags)
return getCombinedScore(b) - getCombinedScore(a);
});
const sourceTasks = createMemo(() => store.tasks.filter(t => !t.deletedAt && matchesFilter(t)));
const { displayedTasks, shakingTaskIds } = useDelayedSort(
sourceTasks,
(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;
// Tie-break with combined score (Priority + Tags)
return getCombinedScore(b) - getCombinedScore(a);
},
300
);
let listRef: HTMLDivElement | undefined;
onMount(() => {
if (listRef) autoAnimate(listRef, { duration: 300, easing: 'ease-out' });
});
return (
@@ -23,13 +32,17 @@ export const UrgencyView: Component = () => {
<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} />}
<div class="grid gap-3" ref={listRef}>
<For each={displayedTasks()}>
{(task) => (
<div class="animate-task-enter">
<TaskCard task={task} isShaking={shakingTaskIds().includes(task.id)} />
</div>
)}
</For>
</div>
{sortedTasks().length === 0 && (
{displayedTasks().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>