diff --git a/app/(dashboard)/tasks/page.tsx b/app/(dashboard)/tasks/page.tsx index a8a0ca2..9aa474e 100644 --- a/app/(dashboard)/tasks/page.tsx +++ b/app/(dashboard)/tasks/page.tsx @@ -1,9 +1,8 @@ import { Suspense } from "react" import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Plus, Search } from "lucide-react" +import { Plus } from "lucide-react" import Link from "next/link" -import { TaskList } from "@/components/task-list" +import { TasksWithSearch } from "@/components/tasks-with-search" import { poppins } from "@/lib/fonts" import { getAllTasks } from "@/app/(dashboard)/tasks/actions" @@ -31,7 +30,7 @@ export default async function TasksPage() { Loading tasks...}> - + ) diff --git a/components/tasks-with-search.tsx b/components/tasks-with-search.tsx new file mode 100644 index 0000000..df0f1fe --- /dev/null +++ b/components/tasks-with-search.tsx @@ -0,0 +1,144 @@ +"use client" + +import { useState, useMemo } from "react" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { TaskList } from "@/components/task-list" +import { Search, X, Filter } from "lucide-react" +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuLabel } from "@/components/ui/dropdown-menu" +import type { Task as PrismaTask, User } from "@/app/generated/prisma/client" + +type TaskWithProfile = PrismaTask & { + assignee?: Pick | null +} + +interface TasksWithSearchProps { + initialTasks: TaskWithProfile[] +} + +const STATUS_OPTIONS = [ + { value: "todo", label: "Todo" }, + { value: "in_progress", label: "In Progress" }, + { value: "review", label: "Review" }, + { value: "done", label: "Done" }, +] + +const PRIORITY_OPTIONS = [ + { value: "high", label: "High" }, + { value: "medium", label: "Medium" }, + { value: "low", label: "Low" }, +] + +export function TasksWithSearch({ initialTasks }: TasksWithSearchProps) { + const [searchQuery, setSearchQuery] = useState("") + const [selectedStatuses, setSelectedStatuses] = useState( + STATUS_OPTIONS.map(opt => opt.value) + ) + const [selectedPriorities, setSelectedPriorities] = useState( + PRIORITY_OPTIONS.map(opt => opt.value) + ) + + const handleClearSearch = () => { + setSearchQuery("") + } + + const toggleStatus = (status: string) => { + setSelectedStatuses(prev => + prev.includes(status) + ? prev.filter(s => s !== status) + : [...prev, status] + ) + } + + const togglePriority = (priority: string) => { + setSelectedPriorities(prev => + prev.includes(priority) + ? prev.filter(p => p !== priority) + : [...prev, priority] + ) + } + + const filteredTasks = useMemo(() => { + return initialTasks.filter(task => { + // Apply search filter + const matchesSearch = searchQuery.trim() === "" || + task.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (task.description || "").toLowerCase().includes(searchQuery.toLowerCase()) + + // Apply status filter + const matchesStatus = selectedStatuses.includes(task.status) + + // Apply priority filter + const matchesPriority = selectedPriorities.includes(task.priority) + + return matchesSearch && matchesStatus && matchesPriority + }) + }, [initialTasks, searchQuery, selectedStatuses, selectedPriorities]) + + return ( +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-10 pr-10" + /> + {searchQuery && ( + + )} +
+ + + + + + Status + {STATUS_OPTIONS.map(option => ( + toggleStatus(option.value)} + > + {option.label} + + ))} + + Priority + {PRIORITY_OPTIONS.map(option => ( + togglePriority(option.value)} + > + {option.label} + + ))} + + +
+ + {filteredTasks.length === 0 ? ( +
+

+ No tasks match the current search and filter criteria. +

+
+ ) : ( + + )} +
+ ) +}