diff --git a/src/components/tasks/TaskList.tsx b/src/components/tasks/TaskList.tsx
index 911d2ea..91347f5 100644
--- a/src/components/tasks/TaskList.tsx
+++ b/src/components/tasks/TaskList.tsx
@@ -1,9 +1,9 @@
"use client";
import { useState, useMemo } from "react";
-import { useSelector } from "react-redux";
-import { RootState, PriorityLevel } from "@/lib/types";
-import { useGetTasksQuery } from "@/lib/services/localApi";
+import { useSelector, useDispatch } from "react-redux";
+import { RootState, PriorityLevel, Task } from "@/lib/types";
+import { useGetTasksQuery, useUpdateTaskMutation } from "@/lib/services/localApi";
import { TaskItem } from "./TaskItem";
import { AddTaskForm } from "./AddTaskForm";
import { TaskSearch } from "./TaskSearch";
@@ -16,15 +16,44 @@ import {
} from "@/lib/utils/priorityUtils";
import { searchTasks } from "@/lib/utils/searchUtils";
import { motion, AnimatePresence } from "framer-motion";
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragEndEvent,
+ DragStartEvent,
+ DragOverlay,
+} from "@dnd-kit/core";
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import { reorderTasks, moveTaskToPriority, clearTaskOrder, reorderMultipleTasks } from "@/lib/features/tasksSlice";
export function TaskList() {
const { data: tasks = [], isLoading, error } = useGetTasksQuery();
const { isAuthenticated } = useSelector((state: RootState) => state.user);
+ const dispatch = useDispatch();
+ const [updateTask] = useUpdateTaskMutation();
const [priorityFilter, setPriorityFilter] = useState<"all" | PriorityLevel>(
"all"
);
const [searchQuery, setSearchQuery] = useState("");
+ const [useCustomOrder, setUseCustomOrder] = useState(false);
+ const [activeId, setActiveId] = useState(null);
+
+ const sensors = useSensors(
+ useSensor(PointerSensor),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
const filteredAndSortedTasks = useMemo(() => {
let filteredTasks = [...tasks];
@@ -39,21 +68,95 @@ export function TaskList() {
});
}
- return filteredTasks.sort((a, b) => {
- if (a.priority !== b.priority) {
- return a.priority - b.priority;
- }
-
- if (!a.dueDate && !b.dueDate) {
- return (
- new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
- );
- }
- if (!a.dueDate) return 1;
- if (!b.dueDate) return -1;
- return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
+ if (useCustomOrder) {
+ // Sort by global order field (allowing items to be positioned above/below items of different priorities)
+ return filteredTasks.sort((a, b) => {
+ const aOrder = a.order ?? Infinity;
+ const bOrder = b.order ?? Infinity;
+ if (aOrder !== bOrder) {
+ return aOrder - bOrder;
+ }
+ // Fallback to createdAt if no order
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+ });
+ } else {
+ // Auto sort: priority → due date → created date
+ return filteredTasks.sort((a, b) => {
+ if (a.priority !== b.priority) {
+ return a.priority - b.priority;
+ }
+
+ if (!a.dueDate && !b.dueDate) {
+ return (
+ new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
+ );
+ }
+ if (!a.dueDate) return 1;
+ if (!b.dueDate) return -1;
+ return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
+ });
+ }
+ }, [tasks, priorityFilter, searchQuery, useCustomOrder]);
+
+ const handleDragStart = (event: DragStartEvent) => {
+ setActiveId(event.active.id as string);
+ };
+
+ const handleDragEnd = async (event: DragEndEvent) => {
+ const { active, over } = event;
+ setActiveId(null);
+
+ if (!over || active.id === over.id) {
+ return;
+ }
+
+ const activeTask = tasks.find((t) => t.id === active.id);
+ if (!activeTask) return;
+
+ const overTask = tasks.find((t) => t.id === over.id);
+ if (!overTask) return;
+
+ // Get all tasks sorted by current order
+ const sortedTasks = [...tasks].sort((a, b) => {
+ const aOrder = a.order ?? Infinity;
+ const bOrder = b.order ?? Infinity;
+ if (aOrder !== bOrder) return aOrder - bOrder;
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
});
- }, [tasks, priorityFilter, searchQuery]);
+
+ const oldIndex = sortedTasks.findIndex((t) => t.id === active.id);
+ const newIndex = sortedTasks.findIndex((t) => t.id === over.id);
+
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) {
+ return;
+ }
+
+ // Reorder tasks
+ const reordered = arrayMove(sortedTasks, oldIndex, newIndex);
+
+ // Update order for all tasks (priority remains unchanged)
+ const orderUpdates = reordered.map((task, index) => ({
+ taskId: task.id,
+ order: index,
+ }));
+
+ dispatch(reorderMultipleTasks(orderUpdates));
+
+ // Persist order updates (priority is not changed)
+ await Promise.all(
+ orderUpdates.map(({ taskId, order }) =>
+ updateTask({ id: taskId, order }).unwrap()
+ )
+ );
+ };
+
+ const handleToggleCustomOrder = () => {
+ if (useCustomOrder) {
+ // Switching to auto sort - clear custom order
+ dispatch(clearTaskOrder());
+ }
+ setUseCustomOrder(!useCustomOrder);
+ };
if (!isAuthenticated) {
return (
@@ -89,11 +192,24 @@ export function TaskList() {
{tasks.length > 0 && (
-
-
+
+
+
+
+
@@ -205,43 +321,76 @@ export function TaskList() {
)}
-
-
- {filteredAndSortedTasks.length === 0 ? (
-
-
- {tasks.length === 0
- ? "No tasks yet"
- : "No tasks match the current filter"}
-
-
- {tasks.length === 0
- ? "Add your first task using the form above!"
- : searchQuery.trim()
- ? "Try adjusting your search or priority filter."
- : "Try changing the priority filter above."}
-
+ {tasks.length === 0
+ ? "No tasks yet"
+ : "No tasks match the current filter"}
+
+
+ {tasks.length === 0
+ ? "Add your first task using the form above!"
+ : searchQuery.trim()
+ ? "Try adjusting your search or priority filter."
+ : "Try changing the priority filter above."}
+